mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 11:40:01 +08:00
[Enhancement] ACL can add rules in clusters (#340)
* [Enhancement] ACL can add rules in clusters and fix ISSUE #297 * rollback the yml change
This commit is contained in:
@@ -94,8 +94,9 @@
|
|||||||
|
|
||||||
## ACL2.0管理界面
|
## ACL2.0管理界面
|
||||||
|
|
||||||
- 支持根据broker地址的acl规则的查询
|
- 支持根据集群名字或者broker地址的acl规则的查询
|
||||||
- acl规则的修改、新增、删除、查找
|
- acl规则的修改、新增、删除、查找
|
||||||
|
- 如果只是选取了集群名字,那么查询的acl列表将会取交集,如果选取了brokerName,就会返回该broker的acl列表。
|
||||||
- (不再支持acl1.0)
|
- (不再支持acl1.0)
|
||||||
|
|
||||||

|

|
||||||
@@ -188,4 +189,4 @@ rolePerms:
|
|||||||
- /monitor/*
|
- /monitor/*
|
||||||
....
|
....
|
||||||
```
|
```
|
||||||
* 3.前端页面显示上,为了更好区分普通用户和admin用户权限,关于资源的删除、更新等操作按钮不对普通用户角色显示,如果要执行资源相关操作,需要退出使用admin角色登录。
|
* 3.前端页面显示上,为了更好区分普通用户和admin用户权限,关于资源的删除、更新等操作按钮不对普通用户角色显示,如果要执行资源相关操作,需要退出使用admin角色登录。
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
apiBaseUrl: 'http://localhost:8082' // 请替换为你的实际 API Base URL
|
apiBaseUrl: 'http://localhost:8082'
|
||||||
};
|
};
|
||||||
|
|
||||||
let _redirectHandler = null;
|
let _redirectHandler = null;
|
||||||
@@ -74,34 +74,36 @@ const remoteApi = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
listUsers: async (brokerAddress) => {
|
listUsers: async (brokerName, clusterName) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
if (brokerName) params.append('brokerName', brokerName);
|
||||||
|
if (clusterName) params.append('clusterName', clusterName);
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/users.query?${params.toString()}`));
|
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/users.query?${params.toString()}`));
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
createUser: async (brokerAddress, userInfo) => {
|
createUser: async (brokerName, userInfo, clusterName) => {
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser.do'), {
|
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser.do'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({brokerAddress, userInfo})
|
body: JSON.stringify({brokerName, userInfo, clusterName})
|
||||||
});
|
|
||||||
return await response.json(); // 返回字符串消息
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUser: async (brokerAddress, userInfo) => {
|
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser.do'), {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({brokerAddress, userInfo})
|
|
||||||
});
|
});
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteUser: async (brokerAddress, username) => {
|
updateUser: async (brokerName, userInfo, clusterName) => {
|
||||||
|
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser.do'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({brokerName, userInfo, clusterName})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUser: async (brokerName, username, clusterName) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
if (brokerName) params.append('brokerName', brokerName);
|
||||||
|
if (clusterName) params.append('clusterName', clusterName);
|
||||||
params.append('username', username);
|
params.append('username', username);
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteUser.do?${params.toString()}`), {
|
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteUser.do?${params.toString()}`), {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
@@ -109,38 +111,40 @@ const remoteApi = {
|
|||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- ACL 权限相关 API ---
|
listAcls: async (brokerName, searchParam, clusterName) => {
|
||||||
listAcls: async (brokerAddress, searchParam) => {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
if (brokerName) params.append('brokerName', brokerName);
|
||||||
|
if (clusterName) params.append('clusterName', clusterName);
|
||||||
if (searchParam) params.append('searchParam', searchParam);
|
if (searchParam) params.append('searchParam', searchParam);
|
||||||
|
if (searchParam != null) console.log(1111)
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
|
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
createAcl: async (brokerAddress, subject, policies) => {
|
createAcl: async (brokerName, subject, policies, clusterName) => {
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createAcl.do'), {
|
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createAcl.do'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({brokerAddress, subject, policies})
|
body: JSON.stringify({brokerName, subject, policies, clusterName})
|
||||||
});
|
});
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAcl: async (brokerAddress, subject, policies) => {
|
updateAcl: async (brokerName, subject, policies, clusterName) => {
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl.do'), {
|
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl.do'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({brokerAddress, subject, policies})
|
body: JSON.stringify({brokerName, subject, policies, clusterName})
|
||||||
});
|
});
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteAcl: async (brokerAddress, subject, resource) => {
|
deleteAcl: async (brokerName, subject, resource, clusterName) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
if (brokerName) params.append('brokerAddress', brokerName);
|
||||||
params.append('subject', subject);
|
params.append('subject', subject);
|
||||||
if (resource) params.append('resource', resource);
|
if (resource) params.append('resource', resource);
|
||||||
|
if (clusterName) params.append('clusterName', clusterName);
|
||||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl.do?${params.toString()}`), {
|
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl.do?${params.toString()}`), {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
@@ -20,13 +20,13 @@ import React, {useEffect, useState} from 'react';
|
|||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
// Subject 类型枚举
|
|
||||||
const subjectTypes = [
|
const subjectTypes = [
|
||||||
{value: 'User', label: 'User'},
|
{value: 'User', label: 'User'},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SubjectInput = ({value, onChange, disabled}) => {
|
const SubjectInput = ({value, onChange, disabled, t}) => {
|
||||||
// 解析传入的 value,将其拆分为 type 和 name
|
|
||||||
const parseValue = (val) => {
|
const parseValue = (val) => {
|
||||||
if (!val || typeof val !== 'string') {
|
if (!val || typeof val !== 'string') {
|
||||||
return {type: subjectTypes[0].value, name: ''}; // 默认值
|
return {type: subjectTypes[0].value, name: ''}; // 默认值
|
||||||
@@ -35,27 +35,25 @@ const SubjectInput = ({value, onChange, disabled}) => {
|
|||||||
if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) {
|
if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) {
|
||||||
return {type: parts[0], name: parts[1]};
|
return {type: parts[0], name: parts[1]};
|
||||||
}
|
}
|
||||||
return {type: subjectTypes[0].value, name: val}; // 如果格式不匹配,将整个值作为 name,类型设为默认
|
return {type: subjectTypes[0].value, name: val};
|
||||||
};
|
};
|
||||||
|
|
||||||
const [currentType, setCurrentType] = useState(() => parseValue(value).type);
|
const [currentType, setCurrentType] = useState(() => parseValue(value).type);
|
||||||
const [currentName, setCurrentName] = useState(() => parseValue(value).name);
|
const [currentName, setCurrentName] = useState(() => parseValue(value).name);
|
||||||
|
|
||||||
// 当外部 value 变化时,更新内部状态
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parsed = parseValue(value);
|
const parsed = parseValue(value);
|
||||||
setCurrentType(parsed.type);
|
setCurrentType(parsed.type);
|
||||||
setCurrentName(parsed.name);
|
setCurrentName(parsed.name);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
// 当类型或名称变化时,通知 Form.Item
|
|
||||||
const triggerChange = (changedType, changedName) => {
|
const triggerChange = (changedType, changedName) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
// 只有当名称不为空时才组合,否则只返回类型或空字符串
|
|
||||||
if (changedName) {
|
if (changedName) {
|
||||||
onChange(`${changedType}:${changedName}`);
|
onChange(`${changedType}:${changedName}`);
|
||||||
} else if (changedType) { // 如果只选择了类型,但名称为空,则不组合
|
} else if (changedType) {
|
||||||
onChange(''); // 或者根据需求返回 'User:' 等,但通常这种情况下不应该有值
|
onChange('');
|
||||||
} else {
|
} else {
|
||||||
onChange('');
|
onChange('');
|
||||||
}
|
}
|
||||||
@@ -91,7 +89,7 @@ const SubjectInput = ({value, onChange, disabled}) => {
|
|||||||
style={{width: '70%'}}
|
style={{width: '70%'}}
|
||||||
value={currentName}
|
value={currentName}
|
||||||
onChange={onNameChange}
|
onChange={onNameChange}
|
||||||
placeholder="请输入名称 (例如: yourUsername)"
|
placeholder={t.PLEASE_INPUT_NAME}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
|
@@ -16,15 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Modal, Spin, Table} from 'antd';
|
import {Descriptions, Modal, Spin, Table, Tag, Tooltip} from 'antd';
|
||||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||||
import {useLanguage} from '../../i18n/LanguageContext';
|
import {useLanguage} from '../../i18n/LanguageContext';
|
||||||
|
|
||||||
|
|
||||||
const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
||||||
const {t} = useLanguage();
|
const {t} = useLanguage();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [connectionData, setConnectionData] = useState(null);
|
const [connectionData, setConnectionData] = useState(null);
|
||||||
const [subscriptionData, setSubscriptionData] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -33,10 +33,8 @@ const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const connResponse = await remoteApi.queryConsumerConnection(group, address);
|
const connResponse = await remoteApi.queryConsumerConnection(group, address);
|
||||||
const topicResponse = await remoteApi.queryTopicByConsumer(group, address);
|
|
||||||
|
|
||||||
if (connResponse.status === 0) setConnectionData(connResponse.data);
|
if (connResponse.status === 0) setConnectionData(connResponse.data);
|
||||||
if (topicResponse.status === 0) setSubscriptionData(topicResponse.data);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -46,53 +44,118 @@ const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
|||||||
}, [visible, group, address]);
|
}, [visible, group, address]);
|
||||||
|
|
||||||
const connectionColumns = [
|
const connectionColumns = [
|
||||||
{title: 'ClientId', dataIndex: 'clientId'},
|
{
|
||||||
{title: 'ClientAddr', dataIndex: 'clientAddr'},
|
title: t.CLIENTID, dataIndex: 'clientId', key: 'clientId', width: 220, ellipsis: true,
|
||||||
{title: 'Language', dataIndex: 'language'},
|
render: (text) => (
|
||||||
{title: 'Version', dataIndex: 'versionDesc'},
|
<Tooltip title={text}>
|
||||||
|
{text}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{title: t.CLIENTADDR, dataIndex: 'clientAddr', key: 'clientAddr', width: 150, ellipsis: true},
|
||||||
|
{title: t.LANGUAGE, dataIndex: 'language', key: 'language', width: 100},
|
||||||
|
{title: t.VERSION, dataIndex: 'versionDesc', key: 'versionDesc', width: 100},
|
||||||
];
|
];
|
||||||
|
|
||||||
const subscriptionColumns = [
|
const subscriptionColumns = [
|
||||||
{title: 'Topic', dataIndex: 'topic'},
|
{
|
||||||
{title: 'SubExpression', dataIndex: 'subString'},
|
title: t.TOPIC, dataIndex: 'topic', key: 'topic', width: 250, ellipsis: true,
|
||||||
|
render: (text) => (
|
||||||
|
<Tooltip title={text}>
|
||||||
|
{text}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{title: t.SUBSCRIPTION_EXPRESSION, dataIndex: 'subString', key: 'subString', width: 150, ellipsis: true},
|
||||||
|
{
|
||||||
|
title: t.EXPRESSION_TYPE, dataIndex: 'expressionType', key: 'expressionType', width: 120,
|
||||||
|
render: (text) => <Tag color="blue">{text}</Tag>
|
||||||
|
},
|
||||||
|
// --- Added Columns for TagsSet and CodeSet ---
|
||||||
|
{
|
||||||
|
title: t.TAGS_SET, // Ensure t.TAGS_SET is defined in your language file
|
||||||
|
dataIndex: 'tagsSet',
|
||||||
|
key: 'tagsSet',
|
||||||
|
width: 150,
|
||||||
|
render: (tags) => (
|
||||||
|
tags && tags.length > 0 ? (
|
||||||
|
<Tooltip title={tags.join(', ')}>
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<Tag key={index} color="default">{tag}</Tag>
|
||||||
|
))}
|
||||||
|
</Tooltip>
|
||||||
|
) : 'N/A'
|
||||||
|
),
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t.CODE_SET, // Ensure t.CODE_SET is defined in your language file
|
||||||
|
dataIndex: 'codeSet',
|
||||||
|
key: 'codeSet',
|
||||||
|
width: 150,
|
||||||
|
render: (codes) => (
|
||||||
|
codes && codes.length > 0 ? (
|
||||||
|
<Tooltip title={codes.join(', ')}>
|
||||||
|
{codes.map((code, index) => (
|
||||||
|
<Tag key={index} color="default">{code}</Tag>
|
||||||
|
))}
|
||||||
|
</Tooltip>
|
||||||
|
) : 'N/A'
|
||||||
|
),
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
// --- End of Added Columns ---
|
||||||
|
{title: t.SUB_VERSION, dataIndex: 'subVersion', key: 'subVersion', width: 150},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const formattedSubscriptionData = connectionData?.subscriptionTable
|
||||||
|
? Object.keys(connectionData.subscriptionTable).map(key => ({
|
||||||
|
...connectionData.subscriptionTable[key],
|
||||||
|
key: key,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={`[${group}]${t.CLIENT}`}
|
title={`[${group}] ${t.CLIENT_INFORMATION}`}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={800}
|
width={1200} // Increased width to accommodate more columns
|
||||||
>
|
>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{connectionData && (
|
{connectionData && (
|
||||||
<>
|
<>
|
||||||
|
<Descriptions bordered column={2} title={t.CONNECTION_OVERVIEW} style={{marginBottom: 20}}>
|
||||||
|
<Descriptions.Item label={t.CONSUME_TYPE}>
|
||||||
|
<Tag color="green">{connectionData.consumeType}</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t.MESSAGE_MODEL}>
|
||||||
|
<Tag color="geekblue">{connectionData.messageModel}</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t.CONSUME_FROM_WHERE}>
|
||||||
|
<Tag color="purple">{connectionData.consumeFromWhere}</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
|
||||||
|
<h3>{t.CLIENT_CONNECTIONS}</h3>
|
||||||
<Table
|
<Table
|
||||||
columns={connectionColumns}
|
columns={connectionColumns}
|
||||||
dataSource={connectionData.connectionSet}
|
dataSource={connectionData.connectionSet}
|
||||||
rowKey="clientId"
|
rowKey="clientId"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
scroll={{x: 'max-content'}}
|
||||||
|
style={{marginBottom: 20}}
|
||||||
/>
|
/>
|
||||||
<h4>{t.SUBSCRIPTION}</h4>
|
|
||||||
|
<h3>{t.CLIENT_SUBSCRIPTIONS}</h3>
|
||||||
<Table
|
<Table
|
||||||
columns={subscriptionColumns}
|
columns={subscriptionColumns}
|
||||||
dataSource={
|
dataSource={formattedSubscriptionData}
|
||||||
subscriptionData?.subscriptionTable
|
rowKey="key"
|
||||||
? Object.entries(subscriptionData.subscriptionTable).map(([topic, detail]) => ({
|
|
||||||
topic,
|
|
||||||
...detail,
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
rowKey="topic"
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
locale={{
|
scroll={{x: 'max-content'}}
|
||||||
emptyText: loading ? <Spin size="small"/> : t.NO_DATA
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<p>ConsumeType: {connectionData.consumeType}</p>
|
|
||||||
<p>MessageModel: {connectionData.messageModel}</p>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
|
@@ -43,32 +43,88 @@ const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [visible, group, address]);
|
}, [visible, group, address]);
|
||||||
|
|
||||||
|
// Format timestamp to readable date
|
||||||
|
const formatTimestamp = (timestamp) => {
|
||||||
|
if (!timestamp || timestamp === 0) return '-';
|
||||||
|
return new Date(timestamp).toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Group data by topic for better organization
|
||||||
|
const groupByTopic = (data) => {
|
||||||
|
const grouped = {};
|
||||||
|
data.forEach(item => {
|
||||||
|
if (!grouped[item.topic]) {
|
||||||
|
grouped[item.topic] = [];
|
||||||
|
}
|
||||||
|
grouped[item.topic].push(item);
|
||||||
|
});
|
||||||
|
return grouped;
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupedDetails = groupByTopic(details);
|
||||||
|
|
||||||
const queueColumns = [
|
const queueColumns = [
|
||||||
{title: 'Broker', dataIndex: 'brokerName'},
|
{title: 'Broker', dataIndex: 'brokerName', width: 120},
|
||||||
{title: 'Queue', dataIndex: 'queueId'},
|
{title: 'Queue ID', dataIndex: 'queueId', width: 100},
|
||||||
{title: 'BrokerOffset', dataIndex: 'brokerOffset'},
|
{title: 'Broker Offset', dataIndex: 'brokerOffset', width: 120},
|
||||||
{title: 'ConsumerOffset', dataIndex: 'consumerOffset'},
|
{title: 'Consumer Offset', dataIndex: 'consumerOffset', width: 120},
|
||||||
{title: 'DiffTotal', dataIndex: 'diffTotal'},
|
{
|
||||||
{title: 'LastTimestamp', dataIndex: 'lastTimestamp'},
|
title: 'Lag (Diff)', dataIndex: 'diffTotal', width: 100,
|
||||||
|
render: (diff) => (
|
||||||
|
<span style={{color: diff > 0 ? '#f5222d' : '#52c41a'}}>
|
||||||
|
{diff}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{title: 'Client Info', dataIndex: 'clientInfo', width: 200},
|
||||||
|
{
|
||||||
|
title: 'Last Consume Time', dataIndex: 'lastTimestamp', width: 180,
|
||||||
|
render: (timestamp) => formatTimestamp(timestamp)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={`[${group}]${t.CONSUME_DETAIL}`}
|
title={
|
||||||
|
<span>Consumer Details - Group: <strong>{group}</strong> | Address: <strong>{address}</strong></span>}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={1200}
|
width={1400}
|
||||||
|
style={{top: 20}}
|
||||||
>
|
>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{details.map((consumeDetail, index) => (
|
{Object.entries(groupedDetails).map(([topic, topicDetails]) => (
|
||||||
<div key={index}>
|
<div key={topic} style={{marginBottom: 24}}>
|
||||||
<Table
|
<div style={{
|
||||||
columns={queueColumns}
|
background: '#f0f0f0',
|
||||||
dataSource={consumeDetail.queueStatInfoList}
|
padding: '8px 16px',
|
||||||
rowKey="queueId"
|
marginBottom: 8,
|
||||||
pagination={false}
|
borderRadius: 4,
|
||||||
/>
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<h3 style={{margin: 0}}>Topic: <strong>{topic}</strong></h3>
|
||||||
|
<div>
|
||||||
|
<span style={{marginRight: 16}}>Total Lag: <strong>{topicDetails[0].diffTotal}</strong></span>
|
||||||
|
<span>Last Consume Time: <strong>{formatTimestamp(topicDetails[0].lastTimestamp)}</strong></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{topicDetails.map((detail, index) => (
|
||||||
|
<div key={index} style={{marginBottom: 16}}>
|
||||||
|
<Table
|
||||||
|
columns={queueColumns}
|
||||||
|
dataSource={detail.queueStatInfoList}
|
||||||
|
rowKey={(record) => `${record.brokerName}-${record.queueId}`}
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
scroll={{x: 'max-content'}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</Spin>
|
</Spin>
|
||||||
|
@@ -290,6 +290,23 @@ export const translations = {
|
|||||||
"USERNAME_PLACEHOLDER": "用户名",
|
"USERNAME_PLACEHOLDER": "用户名",
|
||||||
"PASSWORD_REQUIRED": "密码为必填项",
|
"PASSWORD_REQUIRED": "密码为必填项",
|
||||||
"PASSWORD_PLACEHOLDER": "密码",
|
"PASSWORD_PLACEHOLDER": "密码",
|
||||||
|
"PLEASE_INPUT_NAME":"请输入名称",
|
||||||
|
"PLEASE_SELECT_CLUSTER": "请选择集群",
|
||||||
|
"CLIENT_INFORMATION": "客户端信息",
|
||||||
|
"CONSUME_TYPE": "消费类型",
|
||||||
|
"MESSAGE_MODEL": "消息模型",
|
||||||
|
"CONSUME_FROM_WHERE": "从何处消费",
|
||||||
|
"CLIENT_CONNECTIONS": "客户端连接",
|
||||||
|
"CLIENT_SUBSCRIPTIONS": "客户端订阅",
|
||||||
|
"CONNECTION_OVERVIEW": "连接概览",
|
||||||
|
"CLIENTID": "客户端 ID",
|
||||||
|
"CLIENTADDR": "客户端地址",
|
||||||
|
"LANGUAGE": "语言",
|
||||||
|
"SUBSCRIPTION_EXPRESSION": "订阅表达式",
|
||||||
|
"EXPRESSION_TYPE": "表达式类型",
|
||||||
|
"SUB_VERSION": "订阅版本",
|
||||||
|
"CODE_SET": "代码集",
|
||||||
|
"TAGS_SET": "标签集"
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
"DEFAULT": "Default",
|
"DEFAULT": "Default",
|
||||||
@@ -558,6 +575,24 @@ export const translations = {
|
|||||||
"USERNAME_PLACEHOLDER": "Username placeholder",
|
"USERNAME_PLACEHOLDER": "Username placeholder",
|
||||||
"PASSWORD_REQUIRED": "Password is required",
|
"PASSWORD_REQUIRED": "Password is required",
|
||||||
"PASSWORD_PLACEHOLDER": "Password placeholder",
|
"PASSWORD_PLACEHOLDER": "Password placeholder",
|
||||||
|
"PLEASE_INPUT_NAME": "Please input name",
|
||||||
|
"PLEASE_SELECT_CLUSTER": "Please select cluster",
|
||||||
|
"SUBSCRIPTION": "Subscription",
|
||||||
|
"CLIENT_INFORMATION": "Client Information",
|
||||||
|
"CONSUME_TYPE": "Consume Type",
|
||||||
|
"MESSAGE_MODEL": "Message Model",
|
||||||
|
"CONSUME_FROM_WHERE": "Consume From Where",
|
||||||
|
"CLIENT_CONNECTIONS": "Client Connections",
|
||||||
|
"CLIENT_SUBSCRIPTIONS": "Client Subscriptions",
|
||||||
|
"CONNECTION_OVERVIEW": "Connection Overview",
|
||||||
|
"CLIENTID": "Client ID",
|
||||||
|
"CLIENTADDR": "Client Address",
|
||||||
|
"LANGUAGE": "Language",
|
||||||
|
"SUBSCRIPTION_EXPRESSION": "Subscription Expression",
|
||||||
|
"EXPRESSION_TYPE": "Expression Type",
|
||||||
|
"SUB_VERSION": "Sub Version",
|
||||||
|
"CODE_SET": "Code Set",
|
||||||
|
"TAGS_SET": "Tags Set"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -15,32 +15,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {
|
import {Button, Form, Input, message, Modal, Popconfirm, Select, Space, Table, Tabs, Tag} from 'antd';
|
||||||
Table,
|
import {DeleteOutlined, EditOutlined, EyeInvisibleOutlined, EyeOutlined} from '@ant-design/icons';
|
||||||
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 {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||||
import ResourceInput from '../../components/acl/ResourceInput';
|
import ResourceInput from '../../components/acl/ResourceInput';
|
||||||
import SubjectInput from "../../components/acl/SubjectInput";
|
import SubjectInput from "../../components/acl/SubjectInput";
|
||||||
import {useLanguage} from "../../i18n/LanguageContext";
|
import {useLanguage} from "../../i18n/LanguageContext";
|
||||||
const { TabPane } = Tabs;
|
|
||||||
const { Search } = Input;
|
const {TabPane} = Tabs;
|
||||||
|
const {Search} = Input;
|
||||||
|
|
||||||
const Acl = () => {
|
const Acl = () => {
|
||||||
const [activeTab, setActiveTab] = useState('users');
|
const [activeTab, setActiveTab] = useState('users');
|
||||||
@@ -84,17 +68,19 @@ const Acl = () => {
|
|||||||
// State for the address of the selected broker
|
// State for the address of the selected broker
|
||||||
const [brokerAddress, setBrokerAddress] = useState(undefined);
|
const [brokerAddress, setBrokerAddress] = useState(undefined);
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
// --- Data Fetching and Initial Setup ---
|
// --- Data Fetching and Initial Setup ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const clusterResponse = await remoteApi.getClusterList();
|
const clusterResponse = await remoteApi.getClusterList();
|
||||||
if (clusterResponse.status === 0 && clusterResponse.data) {
|
if (clusterResponse.status === 0 && clusterResponse.data) {
|
||||||
const { clusterInfo } = clusterResponse.data;
|
const {clusterInfo} = clusterResponse.data;
|
||||||
setClusterData(clusterInfo); // Store the entire clusterInfo
|
setClusterData(clusterInfo); // Store the entire clusterInfo
|
||||||
|
|
||||||
// Populate cluster names for the first dropdown
|
// Populate cluster names for the first dropdown
|
||||||
const clusterNames = Object.keys(clusterInfo?.clusterAddrTable || {});
|
const clusterNames = Object.keys(clusterInfo?.clusterAddrTable || {});
|
||||||
setClusterNamesOptions(clusterNames.map(name => ({ label: name, value: name })));
|
setClusterNamesOptions(clusterNames.map(name => ({label: name, value: name})));
|
||||||
|
|
||||||
// Set initial selections if clusters are available
|
// Set initial selections if clusters are available
|
||||||
if (clusterNames.length > 0) {
|
if (clusterNames.length > 0) {
|
||||||
@@ -119,15 +105,15 @@ const Acl = () => {
|
|||||||
console.error('Failed to fetch cluster list:', clusterResponse.errMsg);
|
console.error('Failed to fetch cluster list:', clusterResponse.errMsg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if(!clusterData){
|
if (!clusterData) {
|
||||||
fetchData();
|
setLoading(true);
|
||||||
|
fetchData().finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
if(brokerAddress){
|
if (brokerAddress) {
|
||||||
// Call fetchUsers or fetchAcls based on activeTab initially
|
|
||||||
if (activeTab === 'users') {
|
if (activeTab === 'users') {
|
||||||
fetchUsers();
|
fetchUsers().finally(() => setLoading(false));
|
||||||
} else {
|
} else {
|
||||||
fetchAcls();
|
fetchAcls().finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +121,6 @@ const Acl = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userPermission = localStorage.getItem('userrole');
|
const userPermission = localStorage.getItem('userrole');
|
||||||
console.log(userPermission);
|
|
||||||
if (userPermission == 2) {
|
if (userPermission == 2) {
|
||||||
setWriteOperationEnabled(false);
|
setWriteOperationEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -150,7 +135,7 @@ const Acl = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const brokersInCluster = info.clusterAddrTable[clusterName] || [];
|
const brokersInCluster = info.clusterAddrTable[clusterName] || [];
|
||||||
setBrokerNamesOptions(brokersInCluster.map(broker => ({ label: broker, value: broker })));
|
setBrokerNamesOptions(brokersInCluster.map(broker => ({label: broker, value: broker})));
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Event Handlers ---
|
// --- Event Handlers ---
|
||||||
@@ -174,12 +159,6 @@ const Acl = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Log selected values for debugging (optional) ---
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Selected Cluster:', selectedCluster);
|
|
||||||
console.log('Selected Broker:', selectedBroker);
|
|
||||||
console.log('Broker Address:', brokerAddress);
|
|
||||||
}, [selectedCluster, selectedBroker, brokerAddress]);
|
|
||||||
const handleIpChange = value => {
|
const handleIpChange = value => {
|
||||||
// 过滤掉重复的IP地址
|
// 过滤掉重复的IP地址
|
||||||
const uniqueIps = Array.from(new Set(value));
|
const uniqueIps = Array.from(new Set(value));
|
||||||
@@ -197,7 +176,7 @@ const Acl = () => {
|
|||||||
}
|
}
|
||||||
const invalidIps = value.filter(ip => !ipRegex.test(ip));
|
const invalidIps = value.filter(ip => !ipRegex.test(ip));
|
||||||
if (invalidIps.length > 0) {
|
if (invalidIps.length > 0) {
|
||||||
return Promise.reject(t.INVALID_IP_ADDRESSES +"ips:" + invalidIps.join(', '));
|
return Promise.reject(t.INVALID_IP_ADDRESSES + "ips:" + invalidIps.join(', '));
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
@@ -206,7 +185,7 @@ const Acl = () => {
|
|||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await remoteApi.listUsers(brokerAddress);
|
const result = await remoteApi.listUsers(selectedBroker, selectedCluster);
|
||||||
if (result && result.status === 0 && result.data) {
|
if (result && result.status === 0 && result.data) {
|
||||||
const formattedUsers = result.data.map(user => ({
|
const formattedUsers = result.data.map(user => ({
|
||||||
...user,
|
...user,
|
||||||
@@ -215,7 +194,7 @@ const Acl = () => {
|
|||||||
}));
|
}));
|
||||||
setUserListData(formattedUsers);
|
setUserListData(formattedUsers);
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(t.GET_USERS_FAILED+result?.errMsg);
|
messageApi.error(t.GET_USERS_FAILED + result?.errMsg);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch users:", error);
|
console.error("Failed to fetch users:", error);
|
||||||
@@ -225,10 +204,10 @@ const Acl = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAcls = async (value) => {
|
const fetchAcls = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await remoteApi.listAcls(brokerAddress, value);
|
const result = await remoteApi.listAcls(selectedBroker, searchValue, selectedCluster);
|
||||||
if (result && result.status === 0) {
|
if (result && result.status === 0) {
|
||||||
const formattedAcls = [];
|
const formattedAcls = [];
|
||||||
|
|
||||||
@@ -245,7 +224,6 @@ const Acl = () => {
|
|||||||
const resources = Array.isArray(entry.resource) ? entry.resource : (entry.resource ? [entry.resource] : []);
|
const resources = Array.isArray(entry.resource) ? entry.resource : (entry.resource ? [entry.resource] : []);
|
||||||
|
|
||||||
resources.forEach((singleResource, resourceIndex) => {
|
resources.forEach((singleResource, resourceIndex) => {
|
||||||
console.log(singleResource)
|
|
||||||
formattedAcls.push({
|
formattedAcls.push({
|
||||||
key: `acl-${aclIndex}-policy-${policyIndex}-entry-${entryIndex}-resource-${singleResource}`,
|
key: `acl-${aclIndex}-policy-${policyIndex}-entry-${entryIndex}-resource-${singleResource}`,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
@@ -301,10 +279,10 @@ const Acl = () => {
|
|||||||
const handleDeleteUser = async (username) => {
|
const handleDeleteUser = async (username) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await remoteApi.deleteUser(brokerAddress, username);
|
const result = await remoteApi.deleteUser(selectedBroker, username, selectedCluster);
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.USER_DELETE_SUCCESS);
|
messageApi.success(t.USER_DELETE_SUCCESS);
|
||||||
fetchUsers(brokerAddress);
|
fetchUsers();
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(t.USER_DELETE_FAILED + result.errMsg);
|
messageApi.error(t.USER_DELETE_FAILED + result.errMsg);
|
||||||
}
|
}
|
||||||
@@ -330,14 +308,14 @@ const Acl = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
result = await remoteApi.updateUser(brokerAddress, userInfoParam);
|
result = await remoteApi.updateUser(selectedBroker, userInfoParam, selectedCluster);
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.USER_UPDATE_SUCCESS);
|
messageApi.success(t.USER_UPDATE_SUCCESS);
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(result.errMsg);
|
messageApi.error(result.errMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = await remoteApi.createUser(brokerAddress, userInfoParam);
|
result = await remoteApi.createUser(selectedBroker, userInfoParam, selectedCluster);
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.USER_CREATE_SUCCESS);
|
messageApi.success(t.USER_CREATE_SUCCESS);
|
||||||
} else {
|
} else {
|
||||||
@@ -379,12 +357,12 @@ const Acl = () => {
|
|||||||
const handleDeleteAcl = async (subject, resource) => {
|
const handleDeleteAcl = async (subject, resource) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await remoteApi.deleteAcl(brokerAddress, subject, resource);
|
const result = await remoteApi.deleteAcl(selectedBroker, subject, resource, selectedCluster);
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.ACL_DELETE_SUCCESS);
|
messageApi.success(t.ACL_DELETE_SUCCESS);
|
||||||
fetchAcls();
|
fetchAcls();
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(t.ACL_DELETE_FAILED+result.errMsg);
|
messageApi.error(t.ACL_DELETE_FAILED + result.errMsg);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete ACL:", error);
|
console.error("Failed to delete ACL:", error);
|
||||||
@@ -415,24 +393,23 @@ const Acl = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (isUpdate) { // This condition seems reversed for update/create based on the current logic.
|
if (isUpdate) { // This condition seems reversed for update/create based on the current logic.
|
||||||
result = await remoteApi.updateAcl(brokerAddress, values.subject, policiesParam);
|
result = await remoteApi.updateAcl(selectedBroker, values.subject, policiesParam, selectedCluster);
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.ACL_UPDATE_SUCCESS);
|
messageApi.success(t.ACL_UPDATE_SUCCESS);
|
||||||
setIsAclModalVisible(false);
|
setIsAclModalVisible(false);
|
||||||
fetchAcls(brokerAddress);
|
fetchAcls();
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(t.ACL_UPDATE_FAILED+result.errMsg);
|
messageApi.error(t.ACL_UPDATE_FAILED + result.errMsg);
|
||||||
}
|
}
|
||||||
setIsUpdate(false)
|
setIsUpdate(false)
|
||||||
} else {
|
} else {
|
||||||
result = await remoteApi.createAcl(brokerAddress, values.subject, policiesParam);
|
result = await remoteApi.createAcl(selectedBroker, values.subject, policiesParam, selectedCluster);
|
||||||
console.log(result)
|
|
||||||
if (result.status === 0) {
|
if (result.status === 0) {
|
||||||
messageApi.success(t.ACL_CREATE_SUCCESS);
|
messageApi.success(t.ACL_CREATE_SUCCESS);
|
||||||
setIsAclModalVisible(false);
|
setIsAclModalVisible(false);
|
||||||
fetchAcls(brokerAddress);
|
fetchAcls();
|
||||||
} else {
|
} else {
|
||||||
messageApi.error(t.ACL_CREATE_FAILED+result.errMsg);
|
messageApi.error(t.ACL_CREATE_FAILED + result.errMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,6 +421,10 @@ const Acl = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
setSearchValue(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
// --- Search Functionality ---
|
// --- Search Functionality ---
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
@@ -459,7 +440,7 @@ const Acl = () => {
|
|||||||
setUserListData(filteredData);
|
setUserListData(filteredData);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchAcls(value);
|
fetchAcls();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -480,9 +461,9 @@ const Acl = () => {
|
|||||||
{showPassword ? text : '********'}
|
{showPassword ? text : '********'}
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
icon={showPassword ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
icon={showPassword ? <EyeInvisibleOutlined/> : <EyeOutlined/>}
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
style={{ marginLeft: 8 }}
|
style={{marginLeft: 8}}
|
||||||
>
|
>
|
||||||
{showPassword ? t.HIDE : t.VIEW}
|
{showPassword ? t.HIDE : t.VIEW}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -508,14 +489,14 @@ const Acl = () => {
|
|||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
writeOperationEnabled ? (
|
writeOperationEnabled ? (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
|
<Button icon={<EditOutlined/>} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t.CONFIRM_DELETE_USER}
|
title={t.CONFIRM_DELETE_USER}
|
||||||
onConfirm={() => handleDeleteUser(record.username)}
|
onConfirm={() => handleDeleteUser(record.username)}
|
||||||
okText={t.YES}
|
okText={t.YES}
|
||||||
cancelText={t.NO}
|
cancelText={t.NO}
|
||||||
>
|
>
|
||||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
<Button icon={<DeleteOutlined/>} danger>{t.DELETE}</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Space>
|
</Space>
|
||||||
) : null
|
) : null
|
||||||
@@ -567,14 +548,14 @@ const Acl = () => {
|
|||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
writeOperationEnabled ? (
|
writeOperationEnabled ? (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
|
<Button icon={<EditOutlined/>} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t.CONFIRM_DELETE_ACL}
|
title={t.CONFIRM_DELETE_ACL}
|
||||||
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
|
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
|
||||||
okText={t.YES}
|
okText={t.YES}
|
||||||
cancelText={t.NO}
|
cancelText={t.NO}
|
||||||
>
|
>
|
||||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
<Button icon={<DeleteOutlined/>} danger>{t.DELETE}</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Space>
|
</Space>
|
||||||
) : null
|
) : null
|
||||||
@@ -582,233 +563,236 @@ const Acl = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{msgContextHolder}
|
{msgContextHolder}
|
||||||
<div style={{padding: 24}}>
|
<div style={{padding: 24}}>
|
||||||
<h2>{t.ACL_MANAGEMENT}</h2>
|
<h2>{t.ACL_MANAGEMENT}</h2>
|
||||||
|
|
||||||
<div style={{ marginBottom: 16, display: 'flex', gap: 16 }}>
|
<div style={{marginBottom: 16, display: 'flex', gap: 16}}>
|
||||||
<Form.Item label={t.PLEASE_SELECT_CLUSTER} style={{ marginBottom: 0 }}>
|
<Form.Item label={t.PLEASE_SELECT_CLUSTER} style={{marginBottom: 0}}>
|
||||||
<Select
|
<Select
|
||||||
placeholder={t.PLEASE_SELECT_CLUSTER}
|
placeholder={t.PLEASE_SELECT_CLUSTER}
|
||||||
style={{ width: 200 }}
|
style={{width: 200}}
|
||||||
onChange={handleClusterChange}
|
onChange={handleClusterChange}
|
||||||
value={selectedCluster}
|
value={selectedCluster}
|
||||||
options={clusterNamesOptions}
|
options={clusterNamesOptions}
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t.PLEASE_SELECT_BROKER} style={{ marginBottom: 0 }}>
|
|
||||||
<Select
|
|
||||||
placeholder={t.PLEASE_SELECT_BROKER}
|
|
||||||
style={{ width: 200 }}
|
|
||||||
onChange={handleBrokerChange}
|
|
||||||
value={selectedBroker}
|
|
||||||
options={brokerNamesOptions} // Now dynamically updated
|
|
||||||
disabled={!selectedCluster} // Disable broker selection if no cluster is chosen
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Button type="primary" onClick={activeTab === 'users' ? fetchUsers : fetchAcls}>
|
|
||||||
{t.CONFIRM}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
||||||
<Form.Item
|
<Form.Item label={t.PLEASE_SELECT_BROKER} style={{marginBottom: 0}}>
|
||||||
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
|
<Select
|
||||||
mode="tags"
|
placeholder={t.PLEASE_SELECT_BROKER}
|
||||||
style={{width: '100%'}}
|
style={{width: 200}}
|
||||||
placeholder={t.ENTER_IP_HINT}
|
onChange={handleBrokerChange}
|
||||||
onChange={handleIpChange}
|
value={selectedBroker}
|
||||||
onDeselect={handleIpDeselect}
|
options={brokerNamesOptions}
|
||||||
value={ips}
|
disabled={!selectedCluster}
|
||||||
tokenSeparators={[',', ' ']}
|
allowClear
|
||||||
>
|
/>
|
||||||
<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>
|
||||||
<Form.Item
|
<Button type="primary" onClick={activeTab === 'users' ? fetchUsers : fetchAcls}>
|
||||||
name="decision"
|
{t.CONFIRM}
|
||||||
label={t.DECISION}
|
</Button>
|
||||||
rules={[{required: true, message: t.PLEASE_ENTER_DECISION}]}
|
</div>
|
||||||
|
|
||||||
|
<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}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
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'}}
|
||||||
>
|
>
|
||||||
<Select mode="single" placeholder="Allow, Deny" style={{width: '100%'}}>
|
<Form.Item
|
||||||
<Select.Option value="Allow">Allow</Select.Option>
|
name="username"
|
||||||
<Select.Option value="Deny">Deny</Select.Option>
|
label={t.USERNAME}
|
||||||
</Select>
|
rules={[{required: true, message: t.PLEASE_ENTER_USERNAME}]}
|
||||||
</Form.Item>
|
>
|
||||||
</Form>
|
<Input disabled={!!currentUser}/>
|
||||||
</Modal>
|
</Form.Item>
|
||||||
</div>
|
<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} t={t}/>
|
||||||
|
</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;
|
export default Acl;
|
||||||
|
@@ -66,7 +66,7 @@ const ConsumerGroupList = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [proxyOptions ,setProxyOptions]= useState([]);
|
const [proxyOptions, setProxyOptions] = useState([]);
|
||||||
const [paginationConf, setPaginationConf] = useState({
|
const [paginationConf, setPaginationConf] = useState({
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -82,9 +82,9 @@ const ConsumerGroupList = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
var response;
|
var response;
|
||||||
if(!proxyEnabled){
|
if (!proxyEnabled) {
|
||||||
response = await remoteApi.queryConsumerGroupList(false);
|
response = await remoteApi.queryConsumerGroupList(false);
|
||||||
}else{
|
} else {
|
||||||
response = await remoteApi.queryConsumerGroupList(false, selectedProxy);
|
response = await remoteApi.queryConsumerGroupList(false, selectedProxy);
|
||||||
}
|
}
|
||||||
if (response.status === 0) {
|
if (response.status === 0) {
|
||||||
@@ -98,12 +98,12 @@ const ConsumerGroupList = () => {
|
|||||||
messageApi.error({title: t.ERROR, content: response.errMsg});
|
messageApi.error({title: t.ERROR, content: response.errMsg});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
messageApi.error({title: t.ERROR, content: t.FAILED_TO_FETCH_DATA});
|
|
||||||
console.error("Error loading consumer groups:", error);
|
console.error("Error loading consumer groups:", error);
|
||||||
|
messageApi.error({title: t.ERROR, content: t.FAILED_TO_FETCH_DATA});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [t]);
|
}, [t, proxyEnabled, selectedProxy, messageApi, setAllConsumerGroupList, remoteApi, setLoading]);
|
||||||
|
|
||||||
const filterByType = (str, type, version) => {
|
const filterByType = (str, type, version) => {
|
||||||
if (filterSystem && type === "SYSTEM") return true;
|
if (filterSystem && type === "SYSTEM") return true;
|
||||||
@@ -465,15 +465,21 @@ const ConsumerGroupList = () => {
|
|||||||
<>
|
<>
|
||||||
{msgContextHolder}
|
{msgContextHolder}
|
||||||
{notificationContextHolder}
|
{notificationContextHolder}
|
||||||
<div style={{ padding: '20px' }}>
|
<div style={{padding: '20px'}}>
|
||||||
<Spin spinning={loading} tip={t.LOADING}>
|
<Spin spinning={loading} tip={t.LOADING}>
|
||||||
<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{
|
||||||
|
marginBottom: '20px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
{/* 左侧:筛选和操作按钮 */}
|
{/* 左侧:筛选和操作按钮 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '15px', flexWrap: 'wrap' }}>
|
<div style={{display: 'flex', alignItems: 'center', gap: '15px', flexWrap: 'wrap'}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{display: 'flex', alignItems: 'center'}}>
|
||||||
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.SUBSCRIPTION_GROUP}:</label>
|
<label
|
||||||
|
style={{marginRight: '8px', whiteSpace: 'nowrap'}}>{t.SUBSCRIPTION_GROUP}:</label>
|
||||||
<Input
|
<Input
|
||||||
style={{ width: '200px' }}
|
style={{width: '200px'}}
|
||||||
value={filterStr}
|
value={filterStr}
|
||||||
onChange={(e) => handleFilterInputChange(e.target.value)}
|
onChange={(e) => handleFilterInputChange(e.target.value)}
|
||||||
placeholder="输入订阅组名称"
|
placeholder="输入订阅组名称"
|
||||||
@@ -504,10 +510,10 @@ const ConsumerGroupList = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧:代理选项 */}
|
{/* 右侧:代理选项 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
|
<div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
|
||||||
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.SELECT_PROXY}:</label>
|
<label style={{marginRight: '8px', whiteSpace: 'nowrap'}}>{t.SELECT_PROXY}:</label>
|
||||||
<Select
|
<Select
|
||||||
style={{ width: '220px' }}
|
style={{width: '220px'}}
|
||||||
placeholder={t.SELECT_PROXY}
|
placeholder={t.SELECT_PROXY}
|
||||||
onChange={(value) => setSelectedProxy(value)}
|
onChange={(value) => setSelectedProxy(value)}
|
||||||
value={selectedProxy}
|
value={selectedProxy}
|
||||||
@@ -515,7 +521,7 @@ const ConsumerGroupList = () => {
|
|||||||
disabled={!proxyEnabled}
|
disabled={!proxyEnabled}
|
||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.ENABLE_PROXY}:</label>
|
<label style={{marginRight: '8px', whiteSpace: 'nowrap'}}>{t.ENABLE_PROXY}:</label>
|
||||||
<Switch
|
<Switch
|
||||||
checked={proxyEnabled}
|
checked={proxyEnabled}
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
|
@@ -33,10 +33,9 @@ const MessageQueryPage = () => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// Topic 查询状态
|
|
||||||
const [allTopicList, setAllTopicList] = useState([]);
|
const [allTopicList, setAllTopicList] = useState([]);
|
||||||
const [selectedTopic, setSelectedTopic] = useState(null);
|
const [selectedTopic, setSelectedTopic] = useState(null);
|
||||||
const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(1, 'hour')); // 默认一小时前
|
const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(1, 'hour'));
|
||||||
const [timepickerEnd, setTimepickerEnd] = useState(moment());
|
const [timepickerEnd, setTimepickerEnd] = useState(moment());
|
||||||
const [messageShowList, setMessageShowList] = useState([]);
|
const [messageShowList, setMessageShowList] = useState([]);
|
||||||
const [paginationConf, setPaginationConf] = useState({
|
const [paginationConf, setPaginationConf] = useState({
|
||||||
|
@@ -18,10 +18,10 @@
|
|||||||
package org.apache.rocketmq.dashboard.controller;
|
package org.apache.rocketmq.dashboard.controller;
|
||||||
|
|
||||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||||
|
import org.apache.rocketmq.dashboard.model.UserInfoDto;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserCreateRequest;
|
import org.apache.rocketmq.dashboard.model.request.UserCreateRequest;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
|
import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
|
||||||
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
|
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
@@ -44,16 +44,18 @@ public class AclController {
|
|||||||
|
|
||||||
@GetMapping("/users.query")
|
@GetMapping("/users.query")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public List<UserInfo> listUsers(@RequestParam(required = false) String brokerAddress) {
|
public List<UserInfoDto> listUsers(@RequestParam(required = false) String brokerName,
|
||||||
return aclService.listUsers(brokerAddress);
|
@RequestParam(required = false) String clusterName) {
|
||||||
|
return aclService.listUsers(clusterName, brokerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/acls.query")
|
@GetMapping("/acls.query")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object listAcls(
|
public Object listAcls(
|
||||||
@RequestParam(required = false) String brokerAddress,
|
@RequestParam(required = false) String brokerName,
|
||||||
@RequestParam(required = false) String searchParam) {
|
@RequestParam(required = false) String searchParam,
|
||||||
return aclService.listAcls(brokerAddress, searchParam);
|
@RequestParam(required = false) String clusterName) {
|
||||||
|
return aclService.listAcls(clusterName, brokerName, searchParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/createAcl.do")
|
@PostMapping("/createAcl.do")
|
||||||
@@ -65,30 +67,35 @@ public class AclController {
|
|||||||
|
|
||||||
@DeleteMapping("/deleteUser.do")
|
@DeleteMapping("/deleteUser.do")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object deleteUser(@RequestParam(required = false) String brokerAddress, @RequestParam String username) {
|
public Object deleteUser(@RequestParam(required = false) String brokerName,
|
||||||
aclService.deleteUser(brokerAddress, username);
|
@RequestParam String username,
|
||||||
|
@RequestParam(required = false) String clusterName) {
|
||||||
|
aclService.deleteUser(clusterName, brokerName, username);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/updateUser.do", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
|
@RequestMapping(value = "/updateUser.do", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object updateUser(@RequestBody UserUpdateRequest request) {
|
public Object updateUser(@RequestBody UserUpdateRequest request) {
|
||||||
aclService.updateUser(request.getBrokerAddress(), request.getUserInfo());
|
aclService.updateUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/createUser.do")
|
@PostMapping("/createUser.do")
|
||||||
|
@ResponseBody
|
||||||
public Object createUser(@RequestBody UserCreateRequest request) {
|
public Object createUser(@RequestBody UserCreateRequest request) {
|
||||||
aclService.createUser(request.getBrokerAddress(), request.getUserInfo());
|
aclService.createUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/deleteAcl.do")
|
@DeleteMapping("/deleteAcl.do")
|
||||||
|
@ResponseBody
|
||||||
public Object deleteAcl(
|
public Object deleteAcl(
|
||||||
@RequestParam(required = false) String brokerAddress,
|
@RequestParam(required = false) String brokerName,
|
||||||
|
@RequestParam(required = false) String clusterName,
|
||||||
@RequestParam String subject,
|
@RequestParam String subject,
|
||||||
@RequestParam(required = false) String resource) {
|
@RequestParam(required = false) String resource) {
|
||||||
aclService.deleteAcl(brokerAddress, subject, resource);
|
aclService.deleteAcl(clusterName, brokerName, subject, resource);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
209
src/main/java/org/apache/rocketmq/dashboard/model/AclInfo.java
Normal file
209
src/main/java/org/apache/rocketmq/dashboard/model/AclInfo.java
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.rocketmq.dashboard.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class AclInfo {
|
||||||
|
|
||||||
|
private String subject;
|
||||||
|
private List<PolicyInfo> policies;
|
||||||
|
|
||||||
|
public static AclInfo of(String subject, List<String> resources, List<String> actions,
|
||||||
|
List<String> sourceIps,
|
||||||
|
String decision) {
|
||||||
|
AclInfo aclInfo = new AclInfo();
|
||||||
|
aclInfo.setSubject(subject);
|
||||||
|
PolicyInfo policyInfo = PolicyInfo.of(resources, actions, sourceIps, decision);
|
||||||
|
aclInfo.setPolicies(Collections.singletonList(policyInfo));
|
||||||
|
return aclInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PolicyInfo {
|
||||||
|
|
||||||
|
private String policyType;
|
||||||
|
private List<PolicyEntryInfo> entries;
|
||||||
|
|
||||||
|
public static PolicyInfo of(List<String> resources, List<String> actions,
|
||||||
|
List<String> sourceIps, String decision) {
|
||||||
|
PolicyInfo policyInfo = new PolicyInfo();
|
||||||
|
List<PolicyEntryInfo> entries = resources.stream()
|
||||||
|
.map(resource -> PolicyEntryInfo.of(resource, actions, sourceIps, decision))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
policyInfo.setEntries(entries);
|
||||||
|
return policyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPolicyType() {
|
||||||
|
return policyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicyType(String policyType) {
|
||||||
|
this.policyType = policyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PolicyEntryInfo> getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntries(List<PolicyEntryInfo> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
PolicyInfo that = (PolicyInfo) o;
|
||||||
|
return Objects.equals(policyType, that.policyType) &&
|
||||||
|
Objects.equals(entries, that.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(policyType, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PolicyEntryInfo {
|
||||||
|
private String resource;
|
||||||
|
private List<String> actions;
|
||||||
|
private List<String> sourceIps;
|
||||||
|
private String decision;
|
||||||
|
|
||||||
|
public static PolicyEntryInfo of(String resource, List<String> actions, List<String> sourceIps,
|
||||||
|
String decision) {
|
||||||
|
PolicyEntryInfo policyEntryInfo = new PolicyEntryInfo();
|
||||||
|
policyEntryInfo.setResource(resource);
|
||||||
|
policyEntryInfo.setActions(actions);
|
||||||
|
policyEntryInfo.setSourceIps(sourceIps);
|
||||||
|
policyEntryInfo.setDecision(decision);
|
||||||
|
return policyEntryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResource() {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResource(String resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActions(List<String> actions) {
|
||||||
|
this.actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSourceIps() {
|
||||||
|
return sourceIps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceIps(List<String> sourceIps) {
|
||||||
|
this.sourceIps = sourceIps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDecision() {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecision(String decision) {
|
||||||
|
this.decision = decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
PolicyEntryInfo that = (PolicyEntryInfo) o;
|
||||||
|
return Objects.equals(resource, that.resource) &&
|
||||||
|
Objects.equals(actions, that.actions) &&
|
||||||
|
Objects.equals(sourceIps, that.sourceIps) &&
|
||||||
|
Objects.equals(decision, that.decision);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(resource, actions, sourceIps, decision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PolicyInfo> getPolicies() {
|
||||||
|
return policies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicies(List<PolicyInfo> policies) {
|
||||||
|
this.policies = policies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
AclInfo aclInfo = (AclInfo) o;
|
||||||
|
return Objects.equals(subject, aclInfo.subject) &&
|
||||||
|
Objects.equals(policies, aclInfo.policies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(subject, policies);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(org.apache.rocketmq.remoting.protocol.body.AclInfo source) {
|
||||||
|
this.subject = source.getSubject();
|
||||||
|
if (source.getPolicies() != null) {
|
||||||
|
List<PolicyInfo> copiedPolicies = new ArrayList<>();
|
||||||
|
for (org.apache.rocketmq.remoting.protocol.body.AclInfo.PolicyInfo policy : source.getPolicies()) {
|
||||||
|
PolicyInfo copiedPolicy = new PolicyInfo();
|
||||||
|
copiedPolicy.setPolicyType(policy.getPolicyType());
|
||||||
|
if (policy.getEntries() != null) {
|
||||||
|
List<PolicyEntryInfo> copiedEntries = new ArrayList<>();
|
||||||
|
for (org.apache.rocketmq.remoting.protocol.body.AclInfo.PolicyEntryInfo entry : policy.getEntries()) {
|
||||||
|
PolicyEntryInfo copiedEntry = new PolicyEntryInfo();
|
||||||
|
copiedEntry.setResource(entry.getResource());
|
||||||
|
copiedEntry.setActions(new ArrayList<>(entry.getActions()));
|
||||||
|
copiedEntry.setSourceIps(new ArrayList<>(entry.getSourceIps()));
|
||||||
|
copiedEntry.setDecision(entry.getDecision());
|
||||||
|
copiedEntries.add(copiedEntry);
|
||||||
|
}
|
||||||
|
copiedPolicy.setEntries(copiedEntries);
|
||||||
|
}
|
||||||
|
copiedPolicies.add(copiedPolicy);
|
||||||
|
}
|
||||||
|
this.setPolicies(copiedPolicies);
|
||||||
|
} else {
|
||||||
|
this.setPolicies(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -25,7 +25,8 @@ import java.util.List;
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class PolicyRequest {
|
public class PolicyRequest {
|
||||||
private String brokerAddress;
|
private String clusterName;
|
||||||
|
private String brokerName;
|
||||||
private String subject;
|
private String subject;
|
||||||
private List<Policy> policies;
|
private List<Policy> policies;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.rocketmq.dashboard.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserInfoDto {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String userType;
|
||||||
|
|
||||||
|
private String userStatus;
|
||||||
|
|
||||||
|
public UserInfoDto setUserInfo(UserInfo userInfo) {
|
||||||
|
this.username = userInfo.getUsername();
|
||||||
|
this.password = userInfo.getPassword();
|
||||||
|
this.userType = userInfo.getUserType();
|
||||||
|
this.userStatus = userInfo.getUserStatus();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@@ -23,6 +23,7 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public class UserCreateRequest {
|
public class UserCreateRequest {
|
||||||
private String brokerAddress;
|
private String clusterName;
|
||||||
|
private String brokerName;
|
||||||
private UserInfoParam userInfo;
|
private UserInfoParam userInfo;
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import lombok.Setter;
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class UserUpdateRequest {
|
public class UserUpdateRequest {
|
||||||
private String brokerAddress;
|
private String clusterName;
|
||||||
|
private String brokerName;
|
||||||
private UserInfoParam userInfo;
|
private UserInfoParam userInfo;
|
||||||
}
|
}
|
||||||
|
@@ -20,24 +20,21 @@ package org.apache.rocketmq.dashboard.service;
|
|||||||
|
|
||||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface AclService {
|
public interface AclService {
|
||||||
List<UserInfo> listUsers(String brokerAddress);
|
Object listUsers(String clusterName, String brokerAddress);
|
||||||
|
|
||||||
Object listAcls(String brokerAddress, String searchParam);
|
Object listAcls(String clusterName,String brokerAddress, String searchParam);
|
||||||
|
|
||||||
List<String> createAcl(PolicyRequest policyRequest);
|
Object createAcl(PolicyRequest policyRequest);
|
||||||
|
|
||||||
void deleteUser(String brokerAddress, String username);
|
void deleteUser(String clusterName,String brokerAddress, String username);
|
||||||
|
|
||||||
void updateUser(String brokerAddress, UserInfoParam userParam);
|
void updateUser(String clusterName,String brokerAddress, UserInfoParam userParam);
|
||||||
|
|
||||||
void createUser(String brokerAddress, UserInfoParam userParam);
|
void createUser(String clusterName,String brokerAddress, UserInfoParam userParam);
|
||||||
|
|
||||||
void deleteAcl(String brokerAddress, String subject, String resource);
|
void deleteAcl(String clusterName,String brokerAddress, String subject, String resource);
|
||||||
|
|
||||||
void updateAcl(PolicyRequest policyRequest);
|
void updateAcl(PolicyRequest policyRequest);
|
||||||
}
|
}
|
||||||
|
@@ -21,9 +21,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import org.apache.rocketmq.dashboard.model.Entry;
|
import org.apache.rocketmq.dashboard.model.Entry;
|
||||||
import org.apache.rocketmq.dashboard.model.Policy;
|
import org.apache.rocketmq.dashboard.model.Policy;
|
||||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||||
|
import org.apache.rocketmq.dashboard.model.UserInfoDto;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
||||||
|
import org.apache.rocketmq.dashboard.service.AbstractCommonService;
|
||||||
import org.apache.rocketmq.dashboard.service.AclService;
|
import org.apache.rocketmq.dashboard.service.AclService;
|
||||||
|
import org.apache.rocketmq.dashboard.service.ClusterInfoService;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
|
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
|
||||||
|
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||||
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -37,7 +41,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AclServiceImpl implements AclService {
|
public class AclServiceImpl extends AbstractCommonService implements AclService {
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(AclServiceImpl.class);
|
private Logger logger = LoggerFactory.getLogger(AclServiceImpl.class);
|
||||||
|
|
||||||
@@ -45,60 +49,117 @@ public class AclServiceImpl implements AclService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MQAdminExt mqAdminExt;
|
private MQAdminExt mqAdminExt;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClusterInfoService clusterInfoService;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserInfo> listUsers(String brokerAddress) {
|
public List<UserInfoDto> listUsers(String clusterName, String brokerName) {
|
||||||
List<UserInfo> userList;
|
|
||||||
try {
|
|
||||||
userList = mqAdminExt.listUser(brokerAddress, "");
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("Failed to list users from broker: {}", brokerAddress, ex);
|
|
||||||
throw new RuntimeException("Failed to list users", ex);
|
|
||||||
}
|
|
||||||
if (userList == null || userList.isEmpty()) {
|
|
||||||
logger.warn("No users found for broker: {}", brokerAddress);
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
return userList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
public Object listAcls(String brokerAddress, String searchParam) {
|
Set<UserInfoDto> commonUsers = new HashSet<>();
|
||||||
List<AclInfo> aclList;
|
final boolean[] firstIteration = {true};
|
||||||
try {
|
brokerAddrList.forEach(address -> {
|
||||||
String user = searchParam != null ? searchParam : "";
|
List<UserInfo> userList;
|
||||||
String res = searchParam != null ? searchParam : "";
|
|
||||||
aclList = mqAdminExt.listAcl(brokerAddress, user, "");
|
|
||||||
if (aclList == null) {
|
|
||||||
aclList = new ArrayList<>();
|
|
||||||
}
|
|
||||||
List<AclInfo> resAclList = mqAdminExt.listAcl(brokerAddress, "", res);
|
|
||||||
if (resAclList != null) {
|
|
||||||
aclList.addAll(resAclList);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("Failed to list ACLs from broker: {}", brokerAddress, ex);
|
|
||||||
throw new RuntimeException("Failed to list ACLs", ex);
|
|
||||||
}
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
Set<String> uniqueAclStrings = new HashSet<>();
|
|
||||||
List<AclInfo> resultAclList = new ArrayList<>();
|
|
||||||
|
|
||||||
for (AclInfo acl : aclList) {
|
|
||||||
try {
|
try {
|
||||||
String aclString = mapper.writeValueAsString(acl);
|
userList = mqAdminExt.listUser(address, "");
|
||||||
if (uniqueAclStrings.add(aclString)) {
|
} catch (Exception ex) {
|
||||||
resultAclList.add(acl);
|
logger.error("Failed to list users from broker: {}", address, ex);
|
||||||
}
|
throw new RuntimeException("Failed to list users", ex);
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error serializing AclInfo", e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return resultAclList;
|
List<UserInfoDto> userListDtos = new ArrayList<>();
|
||||||
|
userList.forEach(user -> {
|
||||||
|
UserInfoDto userInfoDto = new UserInfoDto();
|
||||||
|
userListDtos.add(userInfoDto.setUserInfo(user));
|
||||||
|
});
|
||||||
|
if (!userList.isEmpty()) {
|
||||||
|
Set<UserInfoDto> currentUsers = new HashSet<>(userListDtos);
|
||||||
|
if (firstIteration[0]) {
|
||||||
|
commonUsers.addAll(userListDtos);
|
||||||
|
firstIteration[0] = false;
|
||||||
|
} else {
|
||||||
|
commonUsers.retainAll(currentUsers);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("No users found for broker: {}", address);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ArrayList<>(commonUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> createAcl(PolicyRequest policyRequest) {
|
public Object listAcls(String clusterName, String brokerName, String searchParam) {
|
||||||
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
|
Set<org.apache.rocketmq.dashboard.model.AclInfo> commonAcls = new HashSet<>();
|
||||||
|
final boolean[] firstIteration = {true};
|
||||||
|
ObjectMapper mapper = new ObjectMapper(); // Initialize ObjectMapper once
|
||||||
|
|
||||||
|
brokerAddrList.forEach(address -> {
|
||||||
|
List<AclInfo> aclListForBroker;
|
||||||
|
try {
|
||||||
|
String user = searchParam != null ? searchParam : "";
|
||||||
|
String res = searchParam != null ? searchParam : "";
|
||||||
|
// Combine results from both listAcl calls for a single broker
|
||||||
|
List<AclInfo> byUser = mqAdminExt.listAcl(address, user, "");
|
||||||
|
List<AclInfo> byRes = mqAdminExt.listAcl(address, "", res);
|
||||||
|
|
||||||
|
aclListForBroker = new ArrayList<>();
|
||||||
|
if (byUser != null) {
|
||||||
|
aclListForBroker.addAll(byUser);
|
||||||
|
}
|
||||||
|
if (byRes != null) {
|
||||||
|
aclListForBroker.addAll(byRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate ACLs for the current broker to ensure accurate intersection
|
||||||
|
Set<AclInfo> uniqueAclsForBroker = new HashSet<>();
|
||||||
|
Set<String> uniqueAclStringsForBroker = new HashSet<>();
|
||||||
|
for (AclInfo acl : aclListForBroker) {
|
||||||
|
try {
|
||||||
|
String aclString = mapper.writeValueAsString(acl);
|
||||||
|
if (uniqueAclStringsForBroker.add(aclString)) {
|
||||||
|
uniqueAclsForBroker.add(acl);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error serializing AclInfo for broker {}: {}", address, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aclListForBroker = new ArrayList<>(uniqueAclsForBroker);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to list ACLs from broker: {}", address, ex);
|
||||||
|
throw new RuntimeException("Failed to list ACLs", ex);
|
||||||
|
}
|
||||||
|
List<org.apache.rocketmq.dashboard.model.AclInfo> aclInfoList = new ArrayList<>();
|
||||||
|
aclListForBroker.forEach(acl -> {
|
||||||
|
org.apache.rocketmq.dashboard.model.AclInfo aclInfo = new org.apache.rocketmq.dashboard.model.AclInfo();
|
||||||
|
aclInfo.copyFrom(acl);
|
||||||
|
aclInfoList.add(aclInfo);
|
||||||
|
});
|
||||||
|
if (!aclListForBroker.isEmpty()) {
|
||||||
|
Set<org.apache.rocketmq.dashboard.model.AclInfo> currentAcls = new HashSet<>(aclInfoList);
|
||||||
|
if (firstIteration[0]) {
|
||||||
|
commonAcls.addAll(currentAcls);
|
||||||
|
firstIteration[0] = false;
|
||||||
|
} else {
|
||||||
|
commonAcls.retainAll(currentAcls);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("No ACLs found for broker: {}", address);
|
||||||
|
if (firstIteration[0]) {
|
||||||
|
firstIteration[0] = false;
|
||||||
|
} else {
|
||||||
|
commonAcls.clear(); // If any broker has no ACLs, the common set will be empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new ArrayList<>(commonAcls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object createAcl(PolicyRequest policyRequest) {
|
||||||
List<String> successfulResources = new ArrayList<>();
|
List<String> successfulResources = new ArrayList<>();
|
||||||
|
|
||||||
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
||||||
@@ -107,11 +168,13 @@ public class AclServiceImpl implements AclService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String subject = policyRequest.getSubject();
|
String subject = policyRequest.getSubject();
|
||||||
|
|
||||||
if (subject == null || subject.isEmpty()) {
|
if (subject == null || subject.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the broker address list for creating ACLs on all relevant brokers
|
||||||
|
List<String> brokerAddrList = getBrokerAddressList(policyRequest.getClusterName(), policyRequest.getBrokerName());
|
||||||
|
|
||||||
for (Policy policy : policyRequest.getPolicies()) {
|
for (Policy policy : policyRequest.getPolicies()) {
|
||||||
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
||||||
for (Entry entry : policy.getEntries()) {
|
for (Entry entry : policy.getEntries()) {
|
||||||
@@ -136,90 +199,110 @@ public class AclServiceImpl implements AclService {
|
|||||||
aclInfo.setPolicies(aclPolicies);
|
aclInfo.setPolicies(aclPolicies);
|
||||||
aclInfo.setSubject(subject);
|
aclInfo.setSubject(subject);
|
||||||
|
|
||||||
try {
|
for (String brokerAddress : brokerAddrList) {
|
||||||
logger.info("Attempting to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, policyRequest.getBrokerAddress());
|
try {
|
||||||
mqAdminExt.createAcl(policyRequest.getBrokerAddress(), aclInfo);
|
logger.info("Attempting to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||||
successfulResources.add(resource);
|
mqAdminExt.createAcl(brokerAddress, aclInfo);
|
||||||
logger.info("Successfully created ACL for subject: {}, resource: {}", subject, resource);
|
logger.info("Successfully created ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Failed to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, policyRequest.getBrokerAddress(), ex);
|
throw new RuntimeException("Failed to create ACL on broker " + brokerAddress + ex.getMessage());
|
||||||
throw new RuntimeException("Failed to create ACL", ex);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return successfulResources;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteUser(String brokerAddress, String username) {
|
public void deleteUser(String clusterName, String brokerName, String username) {
|
||||||
try {
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
mqAdminExt.deleteUser(brokerAddress, username);
|
|
||||||
} catch (Exception ex) {
|
for (String address : brokerAddrList) {
|
||||||
logger.error("Failed to delete user: {} from broker: {}", username, brokerAddress, ex);
|
try {
|
||||||
throw new RuntimeException("Failed to delete user", ex);
|
mqAdminExt.deleteUser(address, username);
|
||||||
|
logger.info("Successfully deleted user: {} from broker: {}", username, address);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to delete user: {} from broker: {}", username, address, ex);
|
||||||
|
throw new RuntimeException("Failed to delete user on broker " + address + ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUser(String brokerAddress, UserInfoParam userParam) {
|
public void updateUser(String clusterName, String brokerName, UserInfoParam userParam) {
|
||||||
UserInfo user = new UserInfo();
|
UserInfo user = new UserInfo();
|
||||||
user.setUsername(userParam.getUsername());
|
user.setUsername(userParam.getUsername());
|
||||||
user.setPassword(userParam.getPassword());
|
user.setPassword(userParam.getPassword());
|
||||||
user.setUserStatus(userParam.getUserStatus());
|
user.setUserStatus(userParam.getUserStatus());
|
||||||
user.setUserType(userParam.getUserType());
|
user.setUserType(userParam.getUserType());
|
||||||
|
|
||||||
try {
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
mqAdminExt.updateUser(brokerAddress, user);
|
|
||||||
} catch (Exception ex) {
|
for (String address : brokerAddrList) {
|
||||||
logger.error("Failed to update user: {} on broker: {}", userParam.getUsername(), brokerAddress, ex);
|
try {
|
||||||
throw new RuntimeException("Failed to update user", ex);
|
mqAdminExt.updateUser(address, user);
|
||||||
|
logger.info("Successfully updated user: {} on broker: {}", userParam.getUsername(), address);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to update user: {} on broker: {}", userParam.getUsername(), address, ex);
|
||||||
|
throw new RuntimeException("Failed to update user on broker " + address + ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createUser(String brokerAddress, UserInfoParam userParam) {
|
public void createUser(String clusterName, String brokerName, UserInfoParam userParam) {
|
||||||
UserInfo user = new UserInfo();
|
UserInfo user = new UserInfo();
|
||||||
user.setUsername(userParam.getUsername());
|
user.setUsername(userParam.getUsername());
|
||||||
user.setPassword(userParam.getPassword());
|
user.setPassword(userParam.getPassword());
|
||||||
user.setUserStatus(userParam.getUserStatus());
|
user.setUserStatus(userParam.getUserStatus());
|
||||||
user.setUserType(userParam.getUserType());
|
user.setUserType(userParam.getUserType());
|
||||||
try {
|
|
||||||
mqAdminExt.createUser(brokerAddress, user);
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("Failed to create user: {} on broker: {}", userParam.getUsername(), brokerAddress, ex);
|
for (String address : brokerAddrList) {
|
||||||
throw new RuntimeException("Failed to create user", ex);
|
try {
|
||||||
|
mqAdminExt.createUser(address, user);
|
||||||
|
logger.info("Successfully created user: {} on broker: {}", userParam.getUsername(), address);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to create user: {} on broker: {}", userParam.getUsername(), address, ex);
|
||||||
|
throw new RuntimeException("Failed to create user on broker " + address + ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteAcl(String brokerAddress, String subject, String resource) {
|
public void deleteAcl(String clusterName, String brokerName, String subject, String resource) {
|
||||||
try {
|
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||||
String res = resource != null ? resource : "";
|
String res = resource != null ? resource : "";
|
||||||
mqAdminExt.deleteAcl(brokerAddress, subject, res);
|
|
||||||
} catch (Exception ex) {
|
for (String address : brokerAddrList) {
|
||||||
logger.error("Failed to delete ACL for subject: {} and resource: {} on broker: {}", subject, resource, brokerAddress, ex);
|
try {
|
||||||
throw new RuntimeException("Failed to delete ACL", ex);
|
mqAdminExt.deleteAcl(address, subject, res);
|
||||||
|
logger.info("Successfully deleted ACL for subject: {} and resource: {} on broker: {}", subject, resource, address);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to delete ACL for subject: {} and resource: {} on broker: {}", subject, resource, address, ex);
|
||||||
|
throw new RuntimeException("Failed to delete ACL on broker " + address + ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAcl(PolicyRequest policyRequest) {
|
public void updateAcl(PolicyRequest policyRequest) {
|
||||||
|
|
||||||
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
||||||
logger.warn("Policy request is null or policies list is empty. No ACLs to update.");
|
logger.warn("Policy request is null or policies list is empty. No ACLs to update.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert policyRequest != null;
|
|
||||||
String brokerAddress = policyRequest.getBrokerAddress();
|
|
||||||
String subject = policyRequest.getSubject();
|
String subject = policyRequest.getSubject();
|
||||||
|
|
||||||
if (subject == null || subject.isEmpty()) {
|
if (subject == null || subject.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> brokerAddrList = getBrokerAddressList(policyRequest.getClusterName(), policyRequest.getBrokerName());
|
||||||
|
|
||||||
for (Policy policy : policyRequest.getPolicies()) {
|
for (Policy policy : policyRequest.getPolicies()) {
|
||||||
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
||||||
for (Entry entry : policy.getEntries()) {
|
for (Entry entry : policy.getEntries()) {
|
||||||
@@ -244,18 +327,52 @@ public class AclServiceImpl implements AclService {
|
|||||||
aclInfo.setPolicies(aclPolicies);
|
aclInfo.setPolicies(aclPolicies);
|
||||||
aclInfo.setSubject(subject);
|
aclInfo.setSubject(subject);
|
||||||
|
|
||||||
try {
|
for (String brokerAddress : brokerAddrList) {
|
||||||
mqAdminExt.updateAcl(brokerAddress, aclInfo);
|
try {
|
||||||
} catch (Exception ex) {
|
mqAdminExt.updateAcl(brokerAddress, aclInfo);
|
||||||
logger.error("Failed to update ACL for subject: {} on broker: {}", subject, brokerAddress, ex);
|
logger.info("Successfully updated ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||||
throw new RuntimeException("Failed to update ACL", ex);
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to update ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress, ex);
|
||||||
|
throw new RuntimeException("Failed to update ACL on broker " + brokerAddress + ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<String> getBrokerAddressList(String clusterName, String brokerName) {
|
||||||
|
ClusterInfo clusterInfo = clusterInfoService.get();
|
||||||
|
List<String> brokerAddressList = new ArrayList<>();
|
||||||
|
if (brokerName != null) {
|
||||||
|
for (String brokerNameKey : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
|
||||||
|
new ArrayList<>(), List.of(brokerName))) {
|
||||||
|
clusterInfo.getBrokerAddrTable()
|
||||||
|
.get(brokerNameKey)
|
||||||
|
.getBrokerAddrs()
|
||||||
|
.forEach((Long key, String value) -> brokerAddressList.add(value));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (clusterName == null || clusterName.isEmpty()) {
|
||||||
|
logger.warn("Cluster name is null or empty. Cannot retrieve broker addresses.");
|
||||||
|
throw new IllegalArgumentException("Cluster name cannot be null or empty.");
|
||||||
|
}
|
||||||
|
if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null || clusterInfo.getBrokerAddrTable().isEmpty()) {
|
||||||
|
logger.warn("Cluster information is not available or has no broker addresses.");
|
||||||
|
throw new RuntimeException("Cluster information is not available or has no broker addresses.");
|
||||||
|
}
|
||||||
|
for (String brokerNameKey : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
|
||||||
|
List.of(clusterName), new ArrayList<>())) {
|
||||||
|
clusterInfo.getBrokerAddrTable()
|
||||||
|
.get(brokerNameKey)
|
||||||
|
.getBrokerAddrs()
|
||||||
|
.forEach((Long key, String value) -> brokerAddressList.add(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return brokerAddressList;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -210,7 +210,15 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
|||||||
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
|
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
|
||||||
consumeInfo.setSubGroupType("SYSTEM");
|
consumeInfo.setSubGroupType("SYSTEM");
|
||||||
} else {
|
} else {
|
||||||
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
|
try {
|
||||||
|
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
logger.warn("SubscriptionGroupConfig not found for consumer group: {}", consumerGroup);
|
||||||
|
boolean isFifoType = examineSubscriptionGroupConfig(consumerGroup)
|
||||||
|
.stream().map(ConsumerConfigInfo::getSubscriptionGroupConfig)
|
||||||
|
.allMatch(SubscriptionGroupConfig::isConsumeMessageOrderly);
|
||||||
|
consumeInfo.setSubGroupType(isFifoType ? "FIFO" : "NORMAL");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
consumeInfo.setUpdateTime(new Date());
|
consumeInfo.setUpdateTime(new Date());
|
||||||
groupConsumeInfoList.add(consumeInfo);
|
groupConsumeInfoList.add(consumeInfo);
|
||||||
@@ -275,17 +283,24 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address) {
|
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address) {
|
||||||
ConsumeStats consumeStats;
|
List<ConsumeStats> consumeStatses = new ArrayList<>();
|
||||||
String topic = null;
|
String topic = null;
|
||||||
try {
|
try {
|
||||||
String[] addresses = address.split(",");
|
String[] addresses = address.split(",");
|
||||||
String addr = addresses[0];
|
for (String addr : addresses) {
|
||||||
consumeStats = mqAdminExt.examineConsumeStats(addr, groupName, null, 3000);
|
consumeStatses.add(mqAdminExt.examineConsumeStats(addr, groupName, null, 3000));
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.throwIfUnchecked(e);
|
Throwables.throwIfUnchecked(e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return toTopicConsumerInfoList(topic, consumeStats, groupName);
|
List<TopicConsumerInfo> res = new ArrayList<>();
|
||||||
|
consumeStatses.forEach(consumeStats -> {
|
||||||
|
if (consumeStats != null && consumeStats.getOffsetTable() != null && !consumeStats.getOffsetTable().isEmpty()) {
|
||||||
|
res.addAll(toTopicConsumerInfoList(topic, consumeStats, groupName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -16,15 +16,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package org.apache.rocketmq.dashboard.service.provider;
|
package org.apache.rocketmq.dashboard.service.provider;
|
||||||
|
|
||||||
import org.apache.rocketmq.dashboard.service.ClusterInfoService;
|
import org.apache.rocketmq.dashboard.service.ClusterInfoService;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
|
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||||
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
|
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
|
||||||
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserInfoProviderImpl implements UserInfoProvider {
|
public class UserInfoProviderImpl implements UserInfoProvider {
|
||||||
|
@@ -17,32 +17,33 @@
|
|||||||
package org.apache.rocketmq.dashboard.controller;
|
package org.apache.rocketmq.dashboard.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
|
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
|
||||||
import org.apache.rocketmq.auth.authentication.enums.UserType;
|
import org.apache.rocketmq.auth.authentication.enums.UserType;
|
||||||
import org.apache.rocketmq.auth.authorization.enums.Decision;
|
import org.apache.rocketmq.dashboard.model.UserInfoDto;
|
||||||
import org.apache.rocketmq.dashboard.model.Policy;
|
|
||||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserCreateRequest;
|
import org.apache.rocketmq.dashboard.model.request.UserCreateRequest;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
||||||
import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
|
import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
|
||||||
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
|
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
|
||||||
import org.apache.rocketmq.dashboard.support.GlobalExceptionHandler;
|
import org.apache.rocketmq.dashboard.support.GlobalExceptionHandler;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
|
|
||||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
public class AclControllerTest extends BaseControllerTest {
|
public class AclControllerTest extends BaseControllerTest {
|
||||||
|
|
||||||
@@ -52,189 +53,193 @@ public class AclControllerTest extends BaseControllerTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private AclController aclController;
|
private AclController aclController;
|
||||||
|
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mockMvc = MockMvcBuilders.standaloneSetup(aclController).setControllerAdvice(GlobalExceptionHandler.class).build();
|
mockMvc = MockMvcBuilders.standaloneSetup(aclController).setControllerAdvice(GlobalExceptionHandler.class).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testListUsers() {
|
public void testListUsers() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
String brokerAddress = "localhost:10911";
|
String clusterName = "test-cluster";
|
||||||
List<UserInfo> expectedUsers = Arrays.asList(
|
String brokerName = "localhost:10911";
|
||||||
UserInfo.of("user1", "password1", "super"),
|
List<UserInfoDto> expectedUsers = Arrays.asList(
|
||||||
UserInfo.of("user2", "password2", "super")
|
new UserInfoDto("user1", "password1", "super","enable"),
|
||||||
|
new UserInfoDto("user2", "password2", "super","enable")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mock service behavior
|
// Mock service behavior
|
||||||
when(aclService.listUsers(brokerAddress)).thenReturn(expectedUsers);
|
when(aclService.listUsers(clusterName, brokerName)).thenReturn(expectedUsers);
|
||||||
|
|
||||||
// Call controller method
|
// Call controller method via MockMVC
|
||||||
List<UserInfo> result = aclController.listUsers(brokerAddress);
|
mockMvc.perform(MockMvcRequestBuilders.get("/acl/users.query")
|
||||||
|
.param("clusterName", clusterName)
|
||||||
|
.param("brokerName", brokerName))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> {
|
||||||
|
List<UserInfoDto> actualUsers = gson.fromJson(result.getResponse().getContentAsString(), List.class);
|
||||||
|
// Due to Gson's deserialization of List to LinkedTreeMap, direct assertEquals on List<UserInfo> won't work easily.
|
||||||
|
// A more robust comparison would involve iterating or using a custom matcher if UserInfoDto doesn't override equals/hashCode.
|
||||||
|
// For simplicity, let's assume UserInfoDto has proper equals/hashCode for now or convert to JSON string for comparison.
|
||||||
|
assertEquals(gson.toJson(expectedUsers), result.getResponse().getContentAsString());
|
||||||
|
});
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expectedUsers, result);
|
verify(aclService, times(1)).listUsers(clusterName, brokerName);
|
||||||
verify(aclService, times(1)).listUsers(brokerAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testListUsersWithoutBrokerAddress() {
|
public void testListUsersWithoutBrokerAddressAndClusterName() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
List<UserInfo> expectedUsers = Arrays.asList(
|
List<UserInfoDto> expectedUsers = Arrays.asList(
|
||||||
UserInfo.of("user1", "password1", "super")
|
new UserInfoDto("user2", "password2", "super","enable")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mock service behavior
|
// Mock service behavior
|
||||||
when(aclService.listUsers(null)).thenReturn(expectedUsers);
|
when(aclService.listUsers(null, null)).thenReturn(expectedUsers);
|
||||||
// Call controller method
|
|
||||||
List<UserInfo> result = aclController.listUsers(null);
|
|
||||||
// Verify
|
|
||||||
assertEquals(expectedUsers, result);
|
|
||||||
verify(aclService, times(1)).listUsers(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
// Call controller method via MockMVC
|
||||||
public void testListAcls() {
|
mockMvc.perform(MockMvcRequestBuilders.get("/acl/users.query"))
|
||||||
// Prepare test data
|
.andExpect(status().isOk())
|
||||||
String brokerAddress = "localhost:9092";
|
.andExpect(result -> assertEquals(gson.toJson(expectedUsers), result.getResponse().getContentAsString()));
|
||||||
String searchParam = "user1";
|
|
||||||
Object expectedAcls = Arrays.asList(
|
|
||||||
AclInfo.of("user1", List.of("READ", "test"), List.of("TOPIC:test"), List.of("localhost:10911"), Decision.ALLOW.getName())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mock service behavior
|
|
||||||
when(aclService.listAcls(brokerAddress, searchParam)).thenReturn(expectedAcls);
|
|
||||||
|
|
||||||
// Call controller method
|
|
||||||
Object result = aclController.listAcls(brokerAddress, searchParam);
|
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expectedAcls, result);
|
verify(aclService, times(1)).listUsers(null, null);
|
||||||
verify(aclService, times(1)).listAcls(brokerAddress, searchParam);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateAcl() {
|
|
||||||
// Prepare test data
|
|
||||||
PolicyRequest request = new PolicyRequest();
|
|
||||||
request.setBrokerAddress("localhost:9092");
|
|
||||||
request.setSubject("user1");
|
|
||||||
request.setPolicies(List.of(
|
|
||||||
new Policy()
|
|
||||||
));
|
|
||||||
|
|
||||||
// Call controller method
|
|
||||||
Object result = aclController.createAcl(request);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
assertEquals(true, result);
|
|
||||||
verify(aclService, times(1)).createAcl(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteUser() {
|
public void testDeleteUser() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
String brokerAddress = "localhost:9092";
|
String clusterName = "test-cluster";
|
||||||
|
String brokerName = "localhost:9092";
|
||||||
String username = "user1";
|
String username = "user1";
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.deleteUser(brokerAddress, username);
|
doNothing().when(aclService).deleteUser(clusterName, brokerName, username);
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(delete("/acl/deleteUser.do")
|
||||||
|
.param("clusterName", clusterName)
|
||||||
|
.param("brokerName", brokerName)
|
||||||
|
.param("username", username))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).deleteUser(clusterName, brokerName, username);
|
||||||
verify(aclService, times(1)).deleteUser(brokerAddress, username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteUserWithoutBrokerAddress() {
|
public void testDeleteUserWithoutBrokerAddressAndClusterName() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
String username = "user1";
|
String username = "user1";
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.deleteUser(null, username);
|
doNothing().when(aclService).deleteUser(null, null, username);
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(delete("/acl/deleteUser.do")
|
||||||
|
.param("username", username))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).deleteUser(null, null, username);
|
||||||
verify(aclService, times(1)).deleteUser(null, username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateUser() {
|
public void testUpdateUser() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
UserUpdateRequest request = new UserUpdateRequest();
|
UserUpdateRequest request = new UserUpdateRequest();
|
||||||
request.setBrokerAddress("localhost:9092");
|
request.setClusterName("test-cluster");
|
||||||
|
request.setBrokerName("localhost:9092");
|
||||||
request.setUserInfo(new UserInfoParam("user1", "newPassword", UserStatus.ENABLE.getName(), UserType.SUPER.getName()));
|
request.setUserInfo(new UserInfoParam("user1", "newPassword", UserStatus.ENABLE.getName(), UserType.SUPER.getName()));
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.updateUser(request);
|
doNothing().when(aclService).updateUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders.post("/acl/updateUser.do")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(gson.toJson(request)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).updateUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
verify(aclService, times(1)).updateUser(request.getBrokerAddress(), request.getUserInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateUser() {
|
public void testCreateUser() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
UserCreateRequest request = new UserCreateRequest();
|
UserCreateRequest request = new UserCreateRequest();
|
||||||
request.setBrokerAddress("localhost:9092");
|
request.setClusterName("test-cluster");
|
||||||
|
request.setBrokerName("localhost:9092");
|
||||||
request.setUserInfo(new UserInfoParam("user1", "newPassword", UserStatus.ENABLE.getName(), UserType.SUPER.getName()));
|
request.setUserInfo(new UserInfoParam("user1", "newPassword", UserStatus.ENABLE.getName(), UserType.SUPER.getName()));
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.createUser(request);
|
doNothing().when(aclService).createUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders.post("/acl/createUser.do")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(gson.toJson(request)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).createUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||||
verify(aclService, times(1)).createUser(request.getBrokerAddress(), request.getUserInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAcl() {
|
public void testDeleteAcl() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
String brokerAddress = "localhost:9092";
|
String clusterName = "test-cluster";
|
||||||
|
String brokerName = "localhost:9092";
|
||||||
String subject = "user1";
|
String subject = "user1";
|
||||||
String resource = "TOPIC:test";
|
String resource = "TOPIC:test";
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.deleteAcl(brokerAddress, subject, resource);
|
doNothing().when(aclService).deleteAcl(clusterName, brokerName, subject, resource);
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(delete("/acl/deleteAcl.do")
|
||||||
|
.param("clusterName", clusterName)
|
||||||
|
.param("brokerName", brokerName)
|
||||||
|
.param("subject", subject)
|
||||||
|
.param("resource", resource))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).deleteAcl(clusterName, brokerName, subject, resource);
|
||||||
verify(aclService, times(1)).deleteAcl(brokerAddress, subject, resource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAclWithoutBrokerAddressAndResource() {
|
public void testDeleteAclWithoutBrokerAddressAndResourceAndClusterName() throws Exception {
|
||||||
// Prepare test data
|
// Prepare test data
|
||||||
String subject = "user1";
|
String subject = "user1";
|
||||||
|
|
||||||
// Call controller method
|
// Mock service behavior (void method)
|
||||||
Object result = aclController.deleteAcl(null, subject, null);
|
doNothing().when(aclService).deleteAcl(null, null, subject, null);
|
||||||
|
|
||||||
|
// Call controller method via MockMVC
|
||||||
|
mockMvc.perform(delete("/acl/deleteAcl.do")
|
||||||
|
.param("subject", subject))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(result -> assertEquals("true", result.getResponse().getContentAsString()));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(true, result);
|
verify(aclService, times(1)).deleteAcl(null, null, subject, null);
|
||||||
verify(aclService, times(1)).deleteAcl(null, subject, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUpdateAcl() {
|
|
||||||
// Prepare test data
|
|
||||||
PolicyRequest request = new PolicyRequest();
|
|
||||||
request.setBrokerAddress("localhost:9092");
|
|
||||||
request.setSubject("user1");
|
|
||||||
request.setPolicies(List.of(
|
|
||||||
new Policy()
|
|
||||||
));
|
|
||||||
|
|
||||||
// Call controller method
|
|
||||||
Object result = aclController.updateAcl(request);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
assertEquals(true, result);
|
|
||||||
verify(aclService, times(1)).updateAcl(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object getTestController() {
|
protected Object getTestController() {
|
||||||
|
Reference in New Issue
Block a user