[ISSUE #337] Restrict write permissions and update the doc (#338)

This commit is contained in:
Crazylychee
2025-07-07 11:04:48 +08:00
committed by GitHub
parent 07793d8aae
commit c297d059a9
23 changed files with 355 additions and 206 deletions

View File

@@ -77,7 +77,7 @@ const remoteApi = {
listUsers: async (brokerAddress) => {
const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/users.query?${params.toString()}`));
return await response.json();
},

View File

@@ -147,7 +147,7 @@ export const translations = {
"USER_NAME": "用户名",
"PASSWORD": "密码",
"SYSTEM": "系统",
"WELCOME": "您好,欢迎使用RocketMQ仪表盘",
"WELCOME": "欢迎使用RocketMQ仪表盘",
"ENABLE_MESSAGE_TRACE": "开启消息轨迹",
"MESSAGE_TRACE_DETAIL": "消息轨迹详情",
"TRACE_TOPIC": "消息轨迹主题",
@@ -284,6 +284,12 @@ export const translations = {
"PROXY_ENABLED": "代理启用",
"BROKER_OVERVIEW": "Broker概览",
"TOTAL_MSG_RECEIVED_TODAY": "今天接收的总消息数",
"LOGIN_SUCCESS": "登录成功",
"LOGIN_FAILED": "登录失败",
"USERNAME_REQUIRED": "用户名为必填项",
"USERNAME_PLACEHOLDER": "用户名",
"PASSWORD_REQUIRED": "密码为必填项",
"PASSWORD_PLACEHOLDER": "密码",
},
en: {
"DEFAULT": "Default",
@@ -417,7 +423,7 @@ export const translations = {
"USER_NAME": "Username",
"PASSWORD": "Password",
"SYSTEM": "SYSTEM",
"WELCOME": "Hi, welcome using RocketMQ Dashboard",
"WELCOME": "Welcome using RocketMQ Dashboard",
"ENABLE_MESSAGE_TRACE": "Enable Message Trace",
"MESSAGE_TRACE_DETAIL": "Message Trace Detail",
"TRACE_TOPIC": "TraceTopic",
@@ -546,6 +552,12 @@ export const translations = {
"PROXY_ENABLED": "Proxy Enabled",
"BROKER_OVERVIEW": "Broker Overview",
"TOTAL_MSG_RECEIVED_TODAY": "Total messages received today",
"LOGIN_SUCCESS": "Login successful",
"LOGIN_FAILED": "Login failed",
"USERNAME_REQUIRED": "Username is required",
"USERNAME_PLACEHOLDER": "Username placeholder",
"PASSWORD_REQUIRED": "Password is required",
"PASSWORD_PLACEHOLDER": "Password placeholder",
}

View File

@@ -55,6 +55,7 @@ const Acl = () => {
const [showPassword, setShowPassword] = useState(false);
const [isAclModalVisible, setIsAclModalVisible] = useState(false);
const [writeOperationEnabled, setWriteOperationEnabled] = useState(true);
const [currentAcl, setCurrentAcl] = useState(null);
const [aclForm] = Form.useForm();
const [messageApi, msgContextHolder] = message.useMessage();
@@ -132,6 +133,16 @@ const Acl = () => {
}, [activeTab]); // Dependencies for useEffect
useEffect(() => {
const userPermission = localStorage.getItem('userrole');
console.log(userPermission);
if (userPermission == 2) {
setWriteOperationEnabled(false);
} else {
setWriteOperationEnabled(true);
}
}, []);
// --- Helper function to update broker options based on selected cluster ---
const updateBrokerOptions = (clusterName, info = clusterData) => {
if (!info || !info.clusterAddrTable) {
@@ -488,24 +499,26 @@ const Acl = () => {
dataIndex: 'userStatus',
key: 'userStatus',
render: (status) => (
<Tag color={status=== 'enable' ? 'green' : 'red'}>{status}</Tag>
<Tag color={status === 'Enabled' ? 'green' : 'red'}>{status}</Tag>
),
},
{
title: t.OPERATION,
key: 'action',
render: (_, record) => (
<Space size="middle">
<Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
<Popconfirm
title={t.CONFIRM_DELETE_USER}
onConfirm={() => handleDeleteUser(record.username)}
okText={t.YES}
cancelText={t.NO}
>
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
</Popconfirm>
</Space>
writeOperationEnabled ? (
<Space size="middle">
<Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
<Popconfirm
title={t.CONFIRM_DELETE_USER}
onConfirm={() => handleDeleteUser(record.username)}
okText={t.YES}
cancelText={t.NO}
>
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
</Popconfirm>
</Space>
) : null
),
},
];
@@ -552,17 +565,19 @@ const Acl = () => {
title: t.OPERATION,
key: 'action',
render: (_, record) => (
<Space size="middle">
<Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
<Popconfirm
title={t.CONFIRM_DELETE_ACL}
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
okText={t.YES}
cancelText={t.NO}
>
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
</Popconfirm>
</Space>
writeOperationEnabled ? (
<Space size="middle">
<Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
<Popconfirm
title={t.CONFIRM_DELETE_ACL}
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
okText={t.YES}
cancelText={t.NO}
>
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
</Popconfirm>
</Space>
) : null
),
},
];

View File

@@ -232,6 +232,15 @@ const ConsumerGroupList = () => {
return () => clearInterval(intervalId);
}, [intervalProcessSwitch, loadConsumerGroups]);
useEffect(() => {
const userPermission = localStorage.getItem('userrole');
console.log(userPermission);
if (userPermission == 2) {
setWriteOperationEnabled(false);
} else {
setWriteOperationEnabled(true);
}
}, []);
useEffect(() => {
filterList(paginationConf.current, allConsumerGroupList);

View File

@@ -250,7 +250,6 @@ const DashboardPage = () => {
const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable
const brokerDetail = resp.data.brokerServer;
const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, brokerAddrTable);
console.log(brokerAddrTable)
let brokerArray = [];
Object.values(clusterMap).forEach(brokersInCluster => {
brokerArray = brokerArray.concat(brokersInCluster);
@@ -260,8 +259,7 @@ const DashboardPage = () => {
...broker,
key: broker.brokerName,
}));
console.log("即将设置的数据:", newData); // 先打印
setBrokerTableData(newData); // 再设置状态
setBrokerTableData(newData);
brokerArray.sort((firstBroker, lastBroker) => {
const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);

View File

@@ -75,7 +75,7 @@ const DlqMessageQueryPage = () => {
useEffect(() => {
const fetchConsumerGroups = async () => {
setLoading(true);
const resp = await remoteApi.queryConsumerGroupList();
const resp = await remoteApi.queryConsumerGroupList(false);
if (resp.status === 0) {
const filteredGroups = resp.data
.filter(consumerGroup => !consumerGroup.group.startsWith(SYS_GROUP_TOPIC_PREFIX))

View File

@@ -18,23 +18,24 @@
import React from 'react';
import {Button, Form, Input, message, Typography} from 'antd';
import {remoteApi} from "../../api/remoteApi/remoteApi";
import {useLanguage} from "../../i18n/LanguageContext";
const {Title} = Typography;
const Login = () => {
const [form] = Form.useForm();
const [messageApi, msgContextHolder] = message.useMessage();
const {t} = useLanguage();
const onFinish = async (values) => {
const {username, password} = values;
remoteApi.login(username, password).then((res) => {
if (res.status === 0) {
messageApi.success('登录成功');
messageApi.success(t.LOGIN_SUCCESS);
window.localStorage.setItem("username", res.data.loginUserName);
window.localStorage.setItem("userrole", res.data.loginUserRole);
window.location.href = '/';
} else {
messageApi.error(res.message || '登录失败,请检查用户名和密码');
messageApi.error(res.message || t.LOGIN_FAILED);
}
})
};
@@ -50,7 +51,7 @@ const Login = () => {
borderRadius: 8
}}>
<Title level={3} style={{textAlign: 'center', marginBottom: 24}}>
WELCOME
{t.WELCOME}
</Title>
<Form
form={form}
@@ -60,30 +61,27 @@ const Login = () => {
initialValues={{username: '', password: ''}}
>
<Form.Item
label="用户名"
label={t.USERNAME}
name="username"
rules={[{required: true, message: '请输入用户名'}]}
>
<Input placeholder="请输入用户名"/>
rules={[{required: true, message: t.USERNAME_REQUIRED}]}>
<Input placeholder={t.USERNAME_PLACEHOLDER}/>
</Form.Item>
<Form.Item
label="密码"
label={t.PASSWORD}
name="password"
rules={[{required: true, message: '请输入密码'}]}
>
<Input.Password placeholder="请输入密码"/>
rules={[{required: true, message: t.PASSWORD_REQUIRED}]}>
<Input.Password placeholder={t.PASSWORD_PLACEHOLDER}/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
登录
{t.LOGIN}
</Button>
</Form.Item>
</Form>
</div>
</>
);
};

View File

@@ -48,6 +48,16 @@ const Ops = () => {
fetchOpsData();
}, []);
useEffect(() => {
const userPermission = localStorage.getItem('userrole');
console.log(userPermission);
if (userPermission == 2) {
setWriteOperationEnabled(false);
} else {
setWriteOperationEnabled(true);
}
}, []);
const handleUpdateNameSvrAddr = async () => {
if (!selectedNamesrv) {
messageApi.warning('请选择一个 NameServer 地址');

View File

@@ -37,9 +37,13 @@ const ProxyManager = () => {
const [notificationApi, notificationContextHolder] = notification.useNotification();
useEffect(() => {
const userRole = sessionStorage.getItem("userrole");
const isWriteEnabled = userRole === null || userRole === '1';
setWriteOperationEnabled(isWriteEnabled);
const userPermission = localStorage.getItem('userrole');
console.log(userPermission);
if (userPermission == 2) {
setWriteOperationEnabled(false);
} else {
setWriteOperationEnabled(true);
}
}, []);
useEffect(() => {

View File

@@ -100,6 +100,16 @@ const DeployHistoryList = () => {
}, [filterStr, filterNormal, filterDelay, filterFifo, filterTransaction,
filterUnspecified, filterRetry, filterDLQ, filterSystem, allTopicList]);
useEffect(() => {
const userPermission = localStorage.getItem('userrole');
console.log(userPermission);
if (userPermission == 2) {
setWriteOperationEnabled(false);
} else {
setWriteOperationEnabled(true);
}
}, []);
// Close functions for Modals
const closeAddUpdateDialog = () => {
setIsAddUpdateTopicModalVisible(false);