修复一些前端问题 (#1192)

请不要在没有先创建Issue的情况下创建Pull Request。

## 变更的目的是什么

XXXXX

## 简短的更新日志

- [Bugfix]优化系统管理子应用无法正常启动(#1167)
- [Optimize]security下的users、acls接入进权限管理(#1089)
- [BugFix]修复Topic消息展示,offset为0不显示问题(#996)
- [Optimize]Connect操作接入权限管理,所有用户都可以重启、编辑、删除(#1050)
- [Optimize]权限新增ACL,自定义权限配置,资源TransactionalId优化(#1160)
- [Optimize]Connect-JSON模式下的JSON格式和官方API的格式不一致(#1048)
- [Optimize]登录页面star数量变更
- [Optimize]Connect样式优化

## 验证这一变化

XXXX

请遵循此清单,以帮助我们快速轻松地整合您的贡献:

* [ ] 一个 PR(Pull Request的简写)只解决一个问题,禁止一个 PR 解决多个问题;
* [ ] 确保 PR 有对应的 Issue(通常在您开始处理之前创建),除非是书写错误之类的琐碎更改不需要 Issue ;
* [ ] 格式化 PR 及 Commit-Log 的标题及内容,例如 #861 。PS:Commit-Log 需要在 Git Commit
代码时进行填写,在 GitHub 上修改不了;
* [ ] 编写足够详细的 PR 描述,以了解 PR 的作用、方式和原因;
* [ ] 编写必要的单元测试来验证您的逻辑更正。如果提交了新功能或重大更改,请记住在 test 模块中添加 integration-test;
* [ ] 确保编译通过,集成测试通过;
This commit is contained in:
erge
2023-11-26 16:44:00 +08:00
committed by GitHub
13 changed files with 173 additions and 106 deletions

View File

@@ -16,6 +16,13 @@ const babelOptions = {
cacheDirectory: true,
babelrc: false,
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
overrides: [
// TODO编译时需要做的事情更多应该只针对目标第三方库
{
include: './node_modules',
sourceType: 'unambiguous'
}
],
plugins: [
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],

View File

@@ -108,6 +108,7 @@
"optimize-css-assets-webpack-plugin": "^5.0.1",
"prettier": "2.3.2",
"progress-bar-webpack-plugin": "^1.12.1",
"pubsub-js": "^1.9.4",
"query-string": "^7.0.1",
"react-refresh": "^0.10.0",
"react-router-dom": "5.2.1",

View File

@@ -49,7 +49,7 @@ const ConnectDetailCard = (props: { record: any }) => {
return (
<>
{
<span style={{ fontFamily: 'HelveticaNeue-Medium', fontSize: 32, color: '#212529' }}>
<span style={{ fontFamily: 'HelveticaNeue-Medium', fontSize: 28, color: '#212529' }}>
{Utils.firstCharUppercase(type) || '-'}
</span>
}
@@ -64,7 +64,7 @@ const ConnectDetailCard = (props: { record: any }) => {
return (
<>
{
<span style={{ fontFamily: 'HelveticaNeue-Medium', fontSize: 32, color: stateEnum[state].color }}>
<span style={{ fontFamily: 'HelveticaNeue-Medium', fontSize: 28, color: stateEnum[state].color }}>
{Utils.firstCharUppercase(state) || '-'}
</span>
}

View File

@@ -48,6 +48,12 @@ export enum ClustersPermissionMap {
CONNECTOR_DELETE = 'Connector-删除',
CONNECTOR_RESTART = 'Connector-重启',
CONNECTOR_STOP_RESUME = 'Connector-暂停&恢复',
// Security
SECURITY_ACL_ADD = 'Security-ACL新增',
SECURITY_ACL_DELETE = 'Security-ACL删除',
SECURITY_USER_ADD = 'Security-User新增',
SECURITY_USER_DELETE = 'Security-User删除',
SECURITY_USER_EDIT_PASSWORD = 'Security-User修改密码',
}
export interface PermissionNode {
@@ -98,9 +104,7 @@ const CommonConfig = () => {
clustersPermissions.childList.forEach((node: PermissionNode) => node.has && userPermissions.push(node.permissionName));
// 获取用户在系统管理拥有的权限
const configPermissions = userPermissionTree.find(
(sys: PermissionNode) => sys.permissionName === ClustersPermissionMap.SYS_MANAGE
);
const configPermissions = userPermissionTree.find((sys: PermissionNode) => sys.permissionName === ClustersPermissionMap.SYS_MANAGE);
configPermissions &&
configPermissions.childList.forEach((node: PermissionNode) => node.has && userPermissions.push(node.permissionName));

View File

@@ -1,7 +1,7 @@
import api from '@src/api';
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
import customMessage from '@src/components/Message';
import { Button, Divider, Drawer, Form, message, Space, Utils } from 'knowdesign';
import { Button, Divider, Drawer, Form, message, Space, Utils, Select } from 'knowdesign';
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ConnectCluster, ConnectorPlugin, ConnectorPluginConfig, OperateInfo } from './AddConnector';
@@ -9,9 +9,8 @@ import { ConnectCluster, ConnectorPlugin, ConnectorPluginConfig, OperateInfo } f
const PLACEHOLDER = `配置格式如下
{
"connectClusterName": "", // Connect Cluster 名称
"name": "", // Connect Cluster 名称
"config": { // 具体配置项
"name": "",
"connector.class": "",
"tasks.max": 1,
...
@@ -43,11 +42,16 @@ export default forwardRef((props: any, ref) => {
const onOpen = (type: 'create' | 'edit', connectClusterName?: string, defaultConfigs?: { [key: string]: any }) => {
if (defaultConfigs) {
setDefaultConfigs({ ...defaultConfigs, connectClusterName });
const connectorName = connectClusterName;
const connectClusterId = connectClusters.find((cluster) => cluster.label === connectClusterName).value;
form.setFieldsValue({
connectClusterId,
connectorName,
configs: JSON.stringify(
{
connectClusterName,
config: defaultConfigs,
// connectClusterName,
name: defaultConfigs.name,
config: { ...defaultConfigs, name: undefined },
},
null,
2
@@ -63,10 +67,11 @@ export default forwardRef((props: any, ref) => {
form.validateFields().then(
(data) => {
const postData = JSON.parse(data.configs);
postData.connectorName = postData.config.name;
postData.connectClusterId = connectClusters.find((cluster) => cluster.label === postData.connectClusterName).value;
delete postData.connectClusterName;
postData.connectorName = postData.name;
postData.connectClusterId = data.connectClusterId;
postData.config.name = postData.name;
// delete postData.connectClusterName;
delete postData.name;
Object.entries(postData.config).forEach(([key, val]) => {
if (val === null) {
delete postData.config[key];
@@ -161,6 +166,26 @@ export default forwardRef((props: any, ref) => {
}
>
<Form form={form} layout="vertical">
<Form.Item
name="connectClusterId"
label="Connect 集群"
rules={[
{
required: true,
validator(rule, value) {
if (!value) {
return Promise.reject('Connect 集群不能为空');
} else {
return Promise.resolve();
}
},
},
]}
initialValue={defaultConfigs?.connectClusterId}
className="connector-json-connectCluster"
>
<Select options={connectClusters} placeholder="请选择 Connect 集群" disabled={type === 'edit'} />
</Form.Item>
<Form.Item
name="configs"
validateTrigger="onBlur"
@@ -175,40 +200,27 @@ export default forwardRef((props: any, ref) => {
if (typeof v !== 'object') {
return Promise.reject('输入内容必须为 JSON');
}
let connectClusterId = -1;
// 校验 connectClusterName 字段
if (!v.connectClusterName) {
return Promise.reject('内容缺少 connectClusterName 字段或字段内容为空');
let connectClusterId = form.getFieldValue('connectClusterId');
// 校验 connectorName 字段
if (!v.name) {
return Promise.reject('内容缺少 name 项');
} else {
if (type === 'edit') {
if (v.connectClusterName !== defaultConfigs.connectClusterName) {
return Promise.reject('编辑模式下不允许修改 connectClusterName 字段');
}
} else {
if (!connectClusters.length) {
getConnectClusters();
return Promise.reject('connectClusterName 列表获取失败,请重试');
}
const targetConnectCluster = connectClusters.find((cluster) => cluster.label === v.connectClusterName);
if (!targetConnectCluster) {
return Promise.reject('connectClusterName 不存在,请检查');
} else {
connectClusterId = targetConnectCluster.value;
}
if (type === 'edit' && v.name !== defaultConfigs.name) {
return Promise.reject('编辑模式下不允许修改 name 字段');
}
}
if (!v.config || typeof v.config !== 'object') {
return Promise.reject('内容缺少 config 字段或字段格式错误');
} else {
// 校验 connectorName 字段
if (!v.config.name) {
return Promise.reject('config 字段下缺少 name 项');
} else {
if (type === 'edit' && v.config.name !== defaultConfigs.name) {
return Promise.reject('编辑模式下不允许修改 name 字段');
}
}
// // 校验 connectorName 字段
// if (!v.config.name) {
// return Promise.reject('config 字段下缺少 name 项');
// } else {
// if (type === 'edit' && v.config.name !== defaultConfigs.name) {
// return Promise.reject('编辑模式下不允许修改 name 字段');
// }
// }
if (!v.config['connector.class']) {
return Promise.reject('config 字段下缺少 connector.class 项');
} else if (type === 'edit' && v.config['connector.class'] !== defaultConfigs['connector.class']) {
@@ -217,6 +229,10 @@ export default forwardRef((props: any, ref) => {
}
if (type === 'create') {
// 校验创建时是否选择了connect集群
if (!connectClusterId) {
return Promise.reject('请先选择 Connect 集群');
}
// 异步校验 connector 名称是否重复 以及 className 是否存在
return Promise.all([
Utils.request(api.isConnectorExist(connectClusterId, v.config.name)),

View File

@@ -315,6 +315,7 @@ export const getWorkersColumns = (arg?: any) => {
// Detail
export const getConnectorsDetailColumns = (arg?: any) => {
const [global] = AppContainer.useGlobalValue();
const columns = [
{
title: 'Task ID',
@@ -363,16 +364,20 @@ export const getConnectorsDetailColumns = (arg?: any) => {
render: (_t: any, r: any) => {
return (
<div>
<Popconfirm
title="是否重试当前任务?"
onConfirm={() => arg?.retryOption(r.taskId)}
// onCancel={cancel}
okText="是"
cancelText=""
overlayClassName="connect-popconfirm"
>
<a></a>
</Popconfirm>
{global.hasPermission(ClustersPermissionMap.CONNECTOR_RESTART) ? (
<Popconfirm
title="是否重试当前任务?"
onConfirm={() => arg?.retryOption(r.taskId)}
// onCancel={cancel}
okText=""
cancelText="否"
overlayClassName="connect-popconfirm"
>
<a></a>
</Popconfirm>
) : (
<></>
)}
</div>
);
},

View File

@@ -185,9 +185,10 @@
.operate-connector-drawer-use-json {
.CodeMirror.cm-s-default {
height: calc(100vh - 146px);
height: calc(100vh - 196px);
}
.dcloud-form-item {
margin-top: 16px;
margin-bottom: 0 !important;
}
}

View File

@@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom';
import EditTable from '../TestingProduce/component/EditTable';
import Api from '@src/api/index';
import moment from 'moment';
import PubSub from 'pubsub-js'
import PubSub from 'pubsub-js';
const CustomSelectResetTime = (props: { value?: string; onChange?: (val: Number | String) => void }) => {
const { value, onChange } = props;

View File

@@ -13,7 +13,7 @@ const carouselList = [
<img className="carousel-eg-ctr-two-img img-one" src={egTwoContent} />
<div className="carousel-eg-ctr-two-desc desc-one">
<span>Github: </span>
<span>5.8K</span>
<span>6.8K</span>
<span>+ Star的的实时流处理平台</span>
</div>
<div className="carousel-eg-ctr-two-desc desc-two">

View File

@@ -185,7 +185,7 @@
.operate-connector-drawer-use-json {
.CodeMirror.cm-s-default {
height: calc(100vh - 146px);
height: calc(100vh - 196px);
}
.dcloud-form-item {
margin-bottom: 0 !important;

View File

@@ -132,17 +132,35 @@ const AddDrawer = forwardRef((_, ref) => {
form.validateFields().then((formData) => {
const submitData = [];
const { configType, principle, kafkaUser } = formData;
if (configType === 'custom') {
// 1. 自定义权限
// TODO: 需要和后端联调
const { resourceType, resourcePatternType, aclPermissionType, aclOperation, aclClientHost } = formData;
const {
resourceType,
resourcePatternType,
aclPermissionType,
aclOperation,
aclClientHost,
cluster,
topicName,
topicPatternType,
groupName,
groupPatternType,
transactionalId,
transactionalIdPatternType,
} = formData;
submitData.push({
clusterId,
kafkaUser: principle === 'all' ? '*' : kafkaUser,
resourceType,
resourcePatternType,
resourceName: '*',
resourcePatternType: cluster
? 3
: topicPatternType
? topicPatternType
: groupPatternType
? groupPatternType
: transactionalIdPatternType,
resourceName: cluster ? cluster : topicName ? topicName : groupName ? groupName : transactionalId,
aclPermissionType,
aclOperation,
aclClientHost,
@@ -348,37 +366,43 @@ const AddDrawer = forwardRef((_, ref) => {
<Form.Item dependencies={[`${type}Principle`]} style={{ marginBottom: 0 }}>
{({ getFieldValue }) =>
getFieldValue(`${type}Principle`) === 'special' ? (
<Form.Item
name={`${type}Name`}
dependencies={[`${type}PatternType`]}
validateTrigger="onBlur"
rules={[
({ getFieldValue }) => ({
validator: (rule: any, value: string) => {
if (!value) {
return Promise.reject(`${UpperCaseType}Name 不能为空`);
type !== 'transactionalId' ? (
<Form.Item
name={`${type}Name`}
dependencies={[`${type}PatternType`]}
validateTrigger="onBlur"
rules={[
({ getFieldValue }) => ({
validator: (rule: any, value: string) => {
if (!value) {
return Promise.reject(`${UpperCaseType}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;
}
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>
return false;
}}
options={type === 'topic' ? topicMetaData : groupMetaData}
placeholder={`请输入 ${type}Name`}
/>
</Form.Item>
) : (
<Form.Item name={`transactionalId`} rules={[{ required: true, message: `TransactionalId不能为空` }]}>
<Input placeholder={`请输入TransactionalId`}></Input>
</Form.Item>
)
) : null
}
</Form.Item>
@@ -400,7 +424,7 @@ const AddDrawer = forwardRef((_, ref) => {
<Radio value={ACL_PERMISSION_TYPE['Deny']}>Deny</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
{/* <Form.Item
label="Pattern Type"
name="resourcePatternType"
rules={[{ required: true, message: 'Pattern Type 不能为空' }]}
@@ -410,7 +434,7 @@ const AddDrawer = forwardRef((_, ref) => {
<Radio value={ACL_PATTERN_TYPE['Literal']}>Literal</Radio>
<Radio value={ACL_PATTERN_TYPE['Prefixed']}>Prefixed</Radio>
</Radio.Group>
</Form.Item>
</Form.Item> */}
<Form.Item
label="Resource Type"
name="resourceType"
@@ -428,7 +452,7 @@ const AddDrawer = forwardRef((_, ref) => {
<Form.Item dependencies={['resourceType']}>
{({ getFieldValue }) => {
const type = getFieldValue('resourceType');
if (type === ACL_RESOURCE_TYPE['Cluster'] || type === ACL_RESOURCE_TYPE['TransactionalId']) {
if (type === ACL_RESOURCE_TYPE['Cluster']) {
//TODO需要和后端获取集群和事务接口联调
return (
<Form.Item
@@ -438,6 +462,8 @@ const AddDrawer = forwardRef((_, ref) => {
<Input placeholder={`请输入${type === 4 ? 'Cluster名称' : 'TransactionalId'}`}></Input>
</Form.Item>
);
} else if (type === ACL_RESOURCE_TYPE['TransactionalId']) {
return <PatternTypeFormItems type="transactionalId" />;
} else if (type === ACL_RESOURCE_TYPE['Topic']) {
return <PatternTypeFormItems type="topic" />;
} else if (type === ACL_RESOURCE_TYPE['Group']) {

View File

@@ -14,6 +14,7 @@ import AddACLDrawer, {
RESOURCE_TO_OPERATIONS_MAP,
RESOURCE_MAP_KEYS,
} from './EditDrawer';
import { ClustersPermissionMap } from '../CommonConfig';
import './index.less';
const { confirm } = Modal;
@@ -105,7 +106,7 @@ const SecurityACLs = (): JSX.Element => {
};
const columns = () => {
const baseColumns = [
const baseColumns: any = [
{
title: 'Principal',
dataIndex: 'kafkaUser',
@@ -143,7 +144,9 @@ const SecurityACLs = (): JSX.Element => {
title: 'Host',
dataIndex: 'aclClientHost',
},
{
];
if (global.hasPermission && global.hasPermission(ClustersPermissionMap.SECURITY_ACL_DELETE)) {
baseColumns.push({
title: '操作',
dataIndex: '',
width: 120,
@@ -156,8 +159,8 @@ const SecurityACLs = (): JSX.Element => {
</>
);
},
},
];
});
}
return baseColumns;
};
@@ -238,15 +241,19 @@ const SecurityACLs = (): JSX.Element => {
</Form.Item>
</Form>
</div>
<div className={`${tableHeaderPrefix}-right`}>
<Button
type="primary"
// icon={<PlusOutlined />}
onClick={() => editDrawerRef.current.onOpen(true, getACLs)}
>
ACL
</Button>
</div>
{global.hasPermission && global.hasPermission(ClustersPermissionMap.SECURITY_ACL_ADD) ? (
<div className={`${tableHeaderPrefix}-right`}>
<Button
type="primary"
// icon={<PlusOutlined />}
onClick={() => editDrawerRef.current.onOpen(true, getACLs)}
>
ACL
</Button>
</div>
) : (
<></>
)}
</div>
<ProTable
tableProps={{

View File

@@ -82,7 +82,7 @@ export const getTopicMessagesColmns = () => {
dataIndex: 'offset',
key: 'offset',
sorter: true,
render: (t: number) => (+t ? t.toLocaleString() : '-'),
render: (t: number) => (+t || +t === 0 ? t.toLocaleString() : '-'), // TODO: 千分位展示
},
{
title: 'Timestamp',