修复前端新增角色失败等问题 (#1107)

1.新增角色不选择系统管理权限点报错问题;
2.Connect配置项里面涉及敏感字段的值用*号代替;
3.Topic详情、ConsumerGroup详情,ConsumerGroup表格支持手动刷;
4.Topic Message预览,Offset为0不显示数值,添加offset排序;

---------

Co-authored-by: 孙超 <jacksuny@foxmail.com>
Co-authored-by: EricZeng <zengqiao_cn@163.com>
This commit is contained in:
lucasun
2023-08-01 16:34:30 +08:00
committed by GitHub
parent 90e5492060
commit b1892c21e2
10 changed files with 127 additions and 21 deletions

View File

@@ -96,7 +96,7 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
arr.push(permissions[i].id); arr.push(permissions[i].id);
} }
}); });
formData.permissionIdList = formData.permissionIdList.flat(); formData.permissionIdList = formData.permissionIdList.flat().filter((item) => item !== undefined);
setConfirmLoading(true); setConfirmLoading(true);
request(api.editRole, { request(api.editRole, {
method: type === RoleOperate.Add ? 'POST' : 'PUT', method: type === RoleOperate.Add ? 'POST' : 'PUT',
@@ -250,7 +250,7 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
<CheckboxGroupContainer <CheckboxGroupContainer
key={i} key={i}
formInstance={form} formInstance={form}
fieldName="permissionIdList" fieldName={`permissionIdList`}
options={permission.options} options={permission.options}
initSelectedOptions={initSelectedPermissions[permission.id] || []} initSelectedOptions={initSelectedPermissions[permission.id] || []}
groupIdx={i} groupIdx={i}

View File

@@ -34,11 +34,11 @@ module.exports = {
proxy: { proxy: {
'/ks-km/api/v3': { '/ks-km/api/v3': {
changeOrigin: true, changeOrigin: true,
target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/', target: 'http://127.0.0.1/',
}, },
'/logi-security/api/v1': { '/logi-security/api/v1': {
changeOrigin: true, changeOrigin: true,
target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/', target: 'http://127.0.0.1/',
}, },
}, },
}, },

View File

@@ -0,0 +1,10 @@
import { useCallback, useState } from 'react';
export function useForceRefresh() {
const [refreshKey, setRefresh] = useState<number>(0);
const forceRefresh: () => void = useCallback(() => {
setRefresh((x) => x + 1);
}, []);
return [refreshKey, forceRefresh];
}

View File

@@ -816,6 +816,8 @@ const StepFormFifth = (props: SubFormProps) => {
<InputNumber /> <InputNumber />
) : type.toUpperCase() === 'BOOLEAN' ? ( ) : type.toUpperCase() === 'BOOLEAN' ? (
<Switch size="small" /> <Switch size="small" />
) : type.toUpperCase() === 'PASSWORD' ? (
<Input.Password />
) : ( ) : (
<Input /> <Input />
)} )}

View File

@@ -1,12 +1,13 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { Drawer, ProTable, Utils } from 'knowdesign'; import { Button, Space, Divider, Drawer, ProTable, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons'; import { IconFont } from '@knowdesign/icons';
import API from '@src/api/index'; import API from '@src/api/index';
import { defaultPagination, hashDataParse } from '@src/constants/common'; import { defaultPagination, hashDataParse } from '@src/constants/common';
import { getGtoupTopicColumns } from './config'; import { getGtoupTopicColumns } from './config';
import { ExpandedRow } from './ExpandedRow'; import { ExpandedRow } from './ExpandedRow';
import ResetOffsetDrawer from './ResetOffsetDrawer'; import ResetOffsetDrawer from './ResetOffsetDrawer';
import { useForceRefresh } from '@src/components/utils';
const { request } = Utils; const { request } = Utils;
export interface MetricLine { export interface MetricLine {
@@ -63,6 +64,7 @@ const GroupDetail = (props: any) => {
const [openKeys, setOpenKeys] = useState(); const [openKeys, setOpenKeys] = useState();
const [resetOffsetVisible, setResetOffsetVisible] = useState(false); const [resetOffsetVisible, setResetOffsetVisible] = useState(false);
const [resetOffsetArg, setResetOffsetArg] = useState({}); const [resetOffsetArg, setResetOffsetArg] = useState({});
const [refreshKey, forceRefresh] = useForceRefresh();
const genData = async ({ pageNo, pageSize, groupName }: any) => { const genData = async ({ pageNo, pageSize, groupName }: any) => {
if (urlParams?.clusterId === undefined) return; if (urlParams?.clusterId === undefined) return;
@@ -160,7 +162,7 @@ const GroupDetail = (props: any) => {
// // 获取Consumer列表 表格模式 // // 获取Consumer列表 表格模式
// getTopicGroupMetric(hashData); // getTopicGroupMetric(hashData);
// }); // });
}, [hashDataParse(location.hash).groupName]); }, [hashDataParse(location.hash).groupName, refreshKey]);
return ( return (
<Drawer <Drawer
@@ -182,6 +184,14 @@ const GroupDetail = (props: any) => {
// <Divider type="vertical" /> // <Divider type="vertical" />
// </Space> // </Space>
// } // }
extra={
<Space>
<span style={{ display: 'inline-block', fontSize: '15px' }} onClick={forceRefresh as () => void}>
<i className="iconfont icon-shuaxin1" style={{ cursor: 'pointer' }} />
</span>
<Divider type="vertical" />
</Space>
}
> >
<ProTable <ProTable
showQueryForm={false} showQueryForm={false}

View File

@@ -135,6 +135,7 @@ const AddDrawer = forwardRef((_, ref) => {
if (configType === 'custom') { if (configType === 'custom') {
// 1. 自定义权限 // 1. 自定义权限
// TODO: 需要和后端联调
const { resourceType, resourcePatternType, aclPermissionType, aclOperation, aclClientHost } = formData; const { resourceType, resourcePatternType, aclPermissionType, aclOperation, aclClientHost } = formData;
submitData.push({ submitData.push({
clusterId, clusterId,
@@ -281,6 +282,42 @@ const AddDrawer = forwardRef((_, ref) => {
</Form.Item> </Form.Item>
<Form.Item dependencies={['configType']} style={{ marginBottom: 0 }}> <Form.Item dependencies={['configType']} style={{ marginBottom: 0 }}>
{({ getFieldValue }) => { {({ getFieldValue }) => {
const SelectFormItems = (props: { type: string }) => {
const { type } = props;
return (
<Form.Item
name={`${type}Name`}
dependencies={[`${type}PatternType`]}
validateTrigger="onBlur"
rules={[
({ getFieldValue }) => ({
validator: (rule: any, value: string) => {
if (!value) {
return Promise.reject(`${type}Name 不能为空`);
}
if (type === 'topic' && getFieldValue(`${type}PatternType`) === ACL_PATTERN_TYPE['Literal']) {
return Utils.request(api.getTopicMetadata(clusterId as any, value)).then((res: any) => {
return res?.exist ? Promise.resolve() : Promise.reject('该 Topic 不存在');
});
}
return Promise.resolve();
},
}),
]}
>
<AutoComplete
filterOption={(value, option) => {
if (option?.value.includes(value)) {
return true;
}
return false;
}}
options={type === 'topic' ? topicMetaData : groupMetaData}
placeholder={`请输入 ${type}Name`}
/>
</Form.Item>
);
};
const PatternTypeFormItems = (props: { type: string }) => { const PatternTypeFormItems = (props: { type: string }) => {
const { type } = props; const { type } = props;
const UpperCaseType = type[0].toUpperCase() + type.slice(1); const UpperCaseType = type[0].toUpperCase() + type.slice(1);
@@ -388,6 +425,27 @@ const AddDrawer = forwardRef((_, ref) => {
}))} }))}
/> />
</Form.Item> </Form.Item>
<Form.Item dependencies={['resourceType']}>
{({ getFieldValue }) => {
const type = getFieldValue('resourceType');
if (type === ACL_RESOURCE_TYPE['Cluster'] || type === ACL_RESOURCE_TYPE['TransactionalId']) {
//TODO需要和后端获取集群和事务接口联调
return (
<Form.Item
name={`${type === 4 ? 'cluster' : 'transactionalId'}`}
rules={[{ required: true, message: `${type === 4 ? 'Cluster名称' : 'TransactionalId'} 不能为空` }]}
>
<Input placeholder={`请输入${type === 4 ? 'Cluster名称' : 'TransactionalId'}`}></Input>
</Form.Item>
);
} else if (type === ACL_RESOURCE_TYPE['Topic']) {
return <PatternTypeFormItems type="topic" />;
} else if (type === ACL_RESOURCE_TYPE['Group']) {
return <PatternTypeFormItems type="group" />;
}
return null;
}}
</Form.Item>
<Form.Item dependencies={['resourceType']} style={{ marginBottom: 0 }}> <Form.Item dependencies={['resourceType']} style={{ marginBottom: 0 }}>
{({ getFieldValue }) => { {({ getFieldValue }) => {
form.resetFields(['aclOperation']); form.resetFields(['aclOperation']);

View File

@@ -8,6 +8,7 @@ import { useParams } from 'react-router-dom';
import TagsWithHide from '@src/components/TagsWithHide'; import TagsWithHide from '@src/components/TagsWithHide';
import SwitchTab from '@src/components/SwitchTab'; import SwitchTab from '@src/components/SwitchTab';
import RenderEmpty from '@src/components/RenderEmpty'; import RenderEmpty from '@src/components/RenderEmpty';
import { useForceRefresh } from '@src/components/utils';
interface PropsType { interface PropsType {
hashData: any; hashData: any;
@@ -401,11 +402,18 @@ export default (props: PropsType) => {
const { hashData } = props; const { hashData } = props;
const [showMode, setShowMode] = useState<string>('card'); const [showMode, setShowMode] = useState<string>('card');
const [refreshKey, forceRefresh] = useForceRefresh();
return ( return (
<> <>
<div className="brokers-tab-container"> <div className="brokers-tab-container" key={`${refreshKey}`}>
<div className="overview"> <div className="overview">
<div className="left"> <div className="left">
<span
style={{ display: 'inline-block', padding: '0 10px', marginRight: '10px', borderRight: '1px solid #ccc', fontSize: '15px' }}
onClick={forceRefresh as () => void}
>
<i className="iconfont icon-shuaxin1" style={{ cursor: 'pointer' }} />
</span>
<PartitionSummary clusterId={clusterId} topicName={hashData.topicName} /> <PartitionSummary clusterId={clusterId} topicName={hashData.topicName} />
</div> </div>
<div className="cases-box"> <div className="cases-box">

View File

@@ -81,7 +81,8 @@ export const getTopicMessagesColmns = () => {
title: 'Offset', title: 'Offset',
dataIndex: 'offset', dataIndex: 'offset',
key: 'offset', key: 'offset',
render: (t: number) => (t ? t.toLocaleString() : '-'), sorter: true,
render: (t: number) => (+t ? t.toLocaleString() : '-'),
}, },
{ {
title: 'Timestamp', title: 'Timestamp',

View File

@@ -26,6 +26,7 @@
.left { .left {
display: flex; display: flex;
align-items: center;
.info-box { .info-box {
display: flex; display: flex;
height: 36px; height: 36px;

View File

@@ -15,9 +15,21 @@ import Replicator from './Replicator';
import './index.less'; import './index.less';
import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck'; import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck';
import { hashDataParse } from '@src/constants/common'; import { hashDataParse } from '@src/constants/common';
import { useForceRefresh } from '@src/components/utils';
const { TabPane } = Tabs; const { TabPane } = Tabs;
const Reload = (props: any) => {
return (
<span
style={{ display: 'inline-block', padding: '0 10px', marginRight: '10px', borderRight: '1px solid #ccc', fontSize: '15px' }}
onClick={props.forceRefresh as () => void}
>
<i className="iconfont icon-shuaxin1" style={{ cursor: 'pointer' }} />
</span>
);
};
const OperationsSlot: any = { const OperationsSlot: any = {
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
// ['Partitions']: (arg: any) => { // ['Partitions']: (arg: any) => {
@@ -70,17 +82,20 @@ const OperationsSlot: any = {
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
['ConsumerGroups']: (arg: any) => { ['ConsumerGroups']: (arg: any) => {
return ( return (
<SearchInput <>
onSearch={arg.setSearchKeywords} <Reload {...arg} />
attrs={{ <SearchInput
value: arg.searchValue, onSearch={arg.setSearchKeywords}
onChange: arg.setSearchValue, attrs={{
placeholder: '请输入Consumer Group', value: arg.searchValue,
size: 'small', onChange: arg.setSearchValue,
style: { width: '210px', marginRight: '2px' }, placeholder: '请输入Consumer Group',
maxLength: 128, size: 'small',
}} style: { width: '210px', marginRight: '2px' },
/> maxLength: 128,
}}
/>
</>
); );
}, },
}; };
@@ -94,6 +109,7 @@ const TopicDetail = (props: any) => {
const [searchValue, setSearchValue] = useState<string>(''); const [searchValue, setSearchValue] = useState<string>('');
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [hashData, setHashData] = useState<any>({}); const [hashData, setHashData] = useState<any>({});
const [refreshKey, forceRefresh] = useForceRefresh();
const callback = (key: any) => { const callback = (key: any) => {
setSearchValue(''); setSearchValue('');
@@ -184,7 +200,7 @@ const TopicDetail = (props: any) => {
onChange={callback} onChange={callback}
tabBarExtraContent={ tabBarExtraContent={
OperationsSlot[positionType] && OperationsSlot[positionType] &&
OperationsSlot[positionType]({ ...props, setSearchKeywords, setSearchValue, searchValue, positionType }) OperationsSlot[positionType]({ ...props, setSearchKeywords, setSearchValue, searchValue, positionType, forceRefresh })
} }
destroyInactiveTabPane destroyInactiveTabPane
> >
@@ -196,7 +212,7 @@ const TopicDetail = (props: any) => {
</TabPane> </TabPane>
<TabPane tab="ConsumerGroups" key="ConsumerGroups"> <TabPane tab="ConsumerGroups" key="ConsumerGroups">
{positionType === 'ConsumerGroups' && ( {positionType === 'ConsumerGroups' && (
<Consumers searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} /> <Consumers searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} key={`${refreshKey}`} />
)} )}
</TabPane> </TabPane>
<TabPane tab="ACLs" key="ACLs"> <TabPane tab="ACLs" key="ACLs">