mirror of
https://github.com/didi/KnowStreaming.git
synced 2025-12-24 20:22:12 +08:00
合并企业版分支
This commit is contained in:
@@ -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 }],
|
||||
|
||||
@@ -96,7 +96,7 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
|
||||
arr.push(permissions[i].id);
|
||||
}
|
||||
});
|
||||
formData.permissionIdList = formData.permissionIdList.flat();
|
||||
formData.permissionIdList = formData.permissionIdList.flat().filter((item) => item !== undefined);
|
||||
setConfirmLoading(true);
|
||||
request(api.editRole, {
|
||||
method: type === RoleOperate.Add ? 'POST' : 'PUT',
|
||||
@@ -250,7 +250,7 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
|
||||
<CheckboxGroupContainer
|
||||
key={i}
|
||||
formInstance={form}
|
||||
fieldName="permissionIdList"
|
||||
fieldName={`permissionIdList`}
|
||||
options={permission.options}
|
||||
initSelectedOptions={initSelectedPermissions[permission.id] || []}
|
||||
groupIdx={i}
|
||||
|
||||
@@ -34,11 +34,11 @@ module.exports = {
|
||||
proxy: {
|
||||
'/ks-km/api/v3': {
|
||||
changeOrigin: true,
|
||||
target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/',
|
||||
target: 'http://127.0.0.1/',
|
||||
},
|
||||
'/logi-security/api/v1': {
|
||||
changeOrigin: true,
|
||||
target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/',
|
||||
target: 'http://127.0.0.1/',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10004,6 +10004,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pubsub-js": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmmirror.com/pubsub-js/-/pubsub-js-1.9.4.tgz",
|
||||
"integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==",
|
||||
"dev": true
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz",
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/node": "^12.12.25",
|
||||
"@types/pubsub-js": "^1.5.18",
|
||||
"pubsub-js": "^1.5.18",
|
||||
"@typescript-eslint/eslint-plugin": "4.13.0",
|
||||
"@typescript-eslint/parser": "4.13.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
@@ -108,6 +109,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",
|
||||
|
||||
@@ -95,7 +95,7 @@ const api = {
|
||||
getApi(`/clusters/${clusterPhyId}/groups/${groupName}/partitions`),
|
||||
resetGroupOffset: () => getApi('/group-offsets'),
|
||||
getGroupOverview: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`),
|
||||
|
||||
deleteGroupOffset: () => getApi('/group-offsets'),
|
||||
// topics列表
|
||||
getTopicsList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/topics-overview`),
|
||||
getReassignmentList: () => getApi(`/reassignment/topics-overview`),
|
||||
@@ -108,6 +108,7 @@ const api = {
|
||||
getTopicState: (clusterPhyId: number, topicName: string) => getApi(`/clusters/${clusterPhyId}/topics/${topicName}/state`),
|
||||
getTopicMetadata: (clusterPhyId: number, topicName: string) =>
|
||||
getApi(`/clusters/${clusterPhyId}/topics/${topicName}/metadata-combine-exist`),
|
||||
deleteTopicData: () => getApi(`/topics/truncate-topic`),
|
||||
|
||||
// 最新的指标值
|
||||
getMetricPointsLatest: (clusterPhyId: number) => getApi(`/physical-clusters/${clusterPhyId}/latest-metrics`),
|
||||
|
||||
@@ -21,6 +21,7 @@ import { getLicenseInfo } from './constants/common';
|
||||
import api from './api';
|
||||
import ClusterContainer from './pages/index';
|
||||
import ksLogo from './assets/ks-logo.png';
|
||||
import {ClustersPermissionMap} from "./pages/CommonConfig";
|
||||
|
||||
interface ILocaleMap {
|
||||
[index: string]: any;
|
||||
@@ -79,6 +80,9 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
const userInfo = localStorage.getItem('userInfo');
|
||||
const [curActiveAppName, setCurActiveAppName] = useState('');
|
||||
const [versionInfo, setVersionInfo] = useState<VersionInfo>();
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const quickEntries=[];
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname.startsWith('/config')) {
|
||||
setCurActiveAppName('config');
|
||||
@@ -105,6 +109,23 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTERS_MANAGE_VIEW)){
|
||||
quickEntries.push({
|
||||
icon: <IconFont type="icon-duojiqunguanli"/>,
|
||||
txt: '多集群管理',
|
||||
ident: '',
|
||||
active: curActiveAppName === 'cluster',
|
||||
});
|
||||
}
|
||||
if (global.hasPermission && global.hasPermission(ClustersPermissionMap.SYS_MANAGE_VIEW)){
|
||||
quickEntries.push({
|
||||
icon: <IconFont type="icon-xitongguanli" />,
|
||||
txt: '系统管理',
|
||||
ident: 'config',
|
||||
active: curActiveAppName === 'config',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<DProLayout.Container
|
||||
headerProps={{
|
||||
@@ -115,20 +136,7 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
),
|
||||
username: userInfo ? JSON.parse(userInfo)?.userName : '',
|
||||
icon: <DotChartOutlined />,
|
||||
quickEntries: [
|
||||
{
|
||||
icon: <IconFont type="icon-duojiqunguanli" />,
|
||||
txt: '多集群管理',
|
||||
ident: '',
|
||||
active: curActiveAppName === 'cluster',
|
||||
},
|
||||
{
|
||||
icon: <IconFont type="icon-xitongguanli" />,
|
||||
txt: '系统管理',
|
||||
ident: 'config',
|
||||
active: curActiveAppName === 'config',
|
||||
},
|
||||
],
|
||||
quickEntries: quickEntries,
|
||||
isFixed: false,
|
||||
userDropMenuItems: [
|
||||
<Menu.Item key={0}>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import { goLogin } from '@src/constants/axiosConfig';
|
||||
export enum ClustersPermissionMap {
|
||||
CLUSTERS_MANAGE = '多集群管理',
|
||||
CLUSTERS_MANAGE_VIEW = '多集群管理查看',
|
||||
//仅用作隐藏掉系统管理菜单
|
||||
SYS_MANAGE = '系统管理',
|
||||
SYS_MANAGE_VIEW = '系统管理查看',
|
||||
// Cluster
|
||||
CLUSTER_ADD = '接入集群',
|
||||
CLUSTER_DEL = '删除集群',
|
||||
@@ -30,6 +33,9 @@ export enum ClustersPermissionMap {
|
||||
TOPIC_CANCEL_REPLICATOR = 'Topic-详情-取消Topic复制',
|
||||
// Consumers
|
||||
CONSUMERS_RESET_OFFSET = 'Consumers-重置Offset',
|
||||
GROUP_DELETE = 'Group-删除',
|
||||
GROUP_TOPIC_DELETE = 'GroupOffset-Topic纬度删除',
|
||||
GROUP_PARTITION_DELETE = 'GroupOffset-Partition纬度删除',
|
||||
// Test
|
||||
TEST_CONSUMER = 'Test-Consumer',
|
||||
TEST_PRODUCER = 'Test-Producer',
|
||||
@@ -39,6 +45,19 @@ export enum ClustersPermissionMap {
|
||||
MM2_DELETE = 'MM2-删除',
|
||||
MM2_RESTART = 'MM2-重启',
|
||||
MM2_STOP_RESUME = 'MM2-暂停&恢复',
|
||||
// Connector
|
||||
CONNECTOR_ADD = 'Connector-新增',
|
||||
CONNECTOR_CHANGE_CONFIG = 'Connector-编辑',
|
||||
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修改密码',
|
||||
SECURITY_USER_VIEW_PASSWORD = 'Security-User查看密码',
|
||||
}
|
||||
|
||||
export interface PermissionNode {
|
||||
@@ -88,6 +107,11 @@ const CommonConfig = () => {
|
||||
clustersPermissions &&
|
||||
clustersPermissions.childList.forEach((node: PermissionNode) => node.has && userPermissions.push(node.permissionName));
|
||||
|
||||
// 获取用户在系统管理拥有的权限
|
||||
const configPermissions = userPermissionTree.find((sys: PermissionNode) => sys.permissionName === ClustersPermissionMap.SYS_MANAGE);
|
||||
configPermissions &&
|
||||
configPermissions.childList.forEach((node: PermissionNode) => node.has && userPermissions.push(node.permissionName));
|
||||
|
||||
const hasPermission = (permissionName: ClustersPermissionMap) => permissionName && userPermissions.includes(permissionName);
|
||||
|
||||
setGlobal((curState: any) => ({ ...curState, permissions: allPermissions, userPermissions, hasPermission, userInfo }));
|
||||
|
||||
@@ -189,7 +189,14 @@ const StepFormFirst = (props: SubFormProps) => {
|
||||
const result: FormConnectorConfigs = {
|
||||
pluginConfig: {},
|
||||
};
|
||||
|
||||
// 获取一份默认配置
|
||||
const defaultPluginConfig: any = {};
|
||||
|
||||
pluginConfig.configs.forEach(({ definition }) => {
|
||||
// 获取一份默认配置
|
||||
defaultPluginConfig[definition.name] = definition?.defaultValue;
|
||||
|
||||
if (!getExistFormItems(pluginType).includes(definition.name)) {
|
||||
const pluginConfigs = result.pluginConfig;
|
||||
const group = definition.group || 'Others';
|
||||
@@ -205,7 +212,7 @@ const StepFormFirst = (props: SubFormProps) => {
|
||||
|
||||
Object.keys(result).length &&
|
||||
form.setFieldsValue({
|
||||
configs: result,
|
||||
configs: { ...result, defaultPluginConfig, editConnectorConfig: result.connectorConfig },
|
||||
});
|
||||
})
|
||||
.finally(() => props.setSubmitLoading(false));
|
||||
@@ -816,6 +823,8 @@ const StepFormFifth = (props: SubFormProps) => {
|
||||
<InputNumber />
|
||||
) : type.toUpperCase() === 'BOOLEAN' ? (
|
||||
<Switch size="small" />
|
||||
) : type.toUpperCase() === 'PASSWORD' ? (
|
||||
<Input.Password />
|
||||
) : (
|
||||
<Input />
|
||||
)}
|
||||
@@ -947,7 +956,7 @@ export default forwardRef(
|
||||
success?: {
|
||||
connectClusterId: number;
|
||||
connectorName: string;
|
||||
configs: {
|
||||
config: {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
@@ -955,6 +964,7 @@ export default forwardRef(
|
||||
}) => void
|
||||
) => {
|
||||
const promises: Promise<any>[] = [];
|
||||
const compareConfig = stepsFormRef.current[0].getFieldValue('configs'); // 获取步骤一的form信息
|
||||
Object.values(stepsFormRef.current).forEach((form, i) => {
|
||||
const promise = form
|
||||
.validateFields()
|
||||
@@ -985,11 +995,22 @@ export default forwardRef(
|
||||
const [k, ...v] = l.split('=');
|
||||
result[k] = v.join('=');
|
||||
});
|
||||
|
||||
const editConnectorConfig = operateInfo.type === 'edit' ? compareConfig.editConnectorConfig : {}; // 编辑状态时拿到config配置
|
||||
const newCompareConfig = { ...compareConfig.defaultPluginConfig, ...editConnectorConfig, ...result }; // 整合后的表单提交信息
|
||||
Object.keys(newCompareConfig).forEach((item) => {
|
||||
if (
|
||||
newCompareConfig[item] === compareConfig.defaultPluginConfig[item] ||
|
||||
newCompareConfig[item]?.toString() === compareConfig.defaultPluginConfig[item]?.toString()
|
||||
) {
|
||||
delete newCompareConfig[item]; // 清除默认值
|
||||
}
|
||||
});
|
||||
callback({
|
||||
success: {
|
||||
connectClusterId: res[0].connectClusterId,
|
||||
connectorName: result['name'],
|
||||
configs: result,
|
||||
config: newCompareConfig,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -1013,7 +1034,7 @@ export default forwardRef(
|
||||
curClusterName = cluster.label;
|
||||
}
|
||||
});
|
||||
(jsonRef as any)?.onOpen(operateInfo.type, curClusterName, info.success.configs);
|
||||
(jsonRef as any)?.onOpen(operateInfo.type, curClusterName, info.success.config);
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
@@ -1026,9 +1047,9 @@ export default forwardRef(
|
||||
setCurrentStep(info.error);
|
||||
} else {
|
||||
setSubmitLoading(true);
|
||||
Object.entries(info.success.configs).forEach(([key, val]) => {
|
||||
Object.entries(info.success.config).forEach(([key, val]) => {
|
||||
if (val === null) {
|
||||
delete info.success.configs[key];
|
||||
delete info.success.config[key];
|
||||
}
|
||||
});
|
||||
Utils.put(api.validateConnectorConfig, info.success).then(
|
||||
|
||||
@@ -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 名称
|
||||
"configs": { // 具体配置项
|
||||
"name": "",
|
||||
"name": "", // Connect Cluster 名称
|
||||
"config": { // 具体配置项
|
||||
"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,
|
||||
configs: defaultConfigs,
|
||||
// connectClusterName,
|
||||
name: defaultConfigs.name,
|
||||
config: { ...defaultConfigs, name: undefined },
|
||||
},
|
||||
null,
|
||||
2
|
||||
@@ -63,13 +67,14 @@ export default forwardRef((props: any, ref) => {
|
||||
form.validateFields().then(
|
||||
(data) => {
|
||||
const postData = JSON.parse(data.configs);
|
||||
postData.connectorName = postData.configs.name;
|
||||
postData.connectClusterId = connectClusters.find((cluster) => cluster.label === postData.connectClusterName).value;
|
||||
delete postData.connectClusterName;
|
||||
|
||||
Object.entries(postData.configs).forEach(([key, val]) => {
|
||||
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.configs[key];
|
||||
delete postData.config[key];
|
||||
}
|
||||
});
|
||||
Utils.put(api.validateConnectorConfig, postData).then(
|
||||
@@ -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,57 +200,48 @@ 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.configs || typeof v.configs !== 'object') {
|
||||
return Promise.reject('内容缺少 configs 字段或字段格式错误');
|
||||
if (!v.config || typeof v.config !== 'object') {
|
||||
return Promise.reject('内容缺少 config 字段或字段格式错误');
|
||||
} else {
|
||||
// 校验 connectorName 字段
|
||||
if (!v.configs.name) {
|
||||
return Promise.reject('configs 字段下缺少 name 项');
|
||||
} else {
|
||||
if (type === 'edit' && v.configs.name !== defaultConfigs.name) {
|
||||
return Promise.reject('编辑模式下不允许修改 name 字段');
|
||||
}
|
||||
}
|
||||
if (!v.configs['connector.class']) {
|
||||
return Promise.reject('configs 字段下缺少 connector.class 项');
|
||||
} else if (type === 'edit' && v.configs['connector.class'] !== defaultConfigs['connector.class']) {
|
||||
// // 校验 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']) {
|
||||
return Promise.reject('编辑模式下不允许修改 connector.class 字段');
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'create') {
|
||||
// 校验创建时是否选择了connect集群
|
||||
if (!connectClusterId) {
|
||||
return Promise.reject('请先选择 Connect 集群');
|
||||
}
|
||||
// 异步校验 connector 名称是否重复 以及 className 是否存在
|
||||
return Promise.all([
|
||||
Utils.request(api.isConnectorExist(connectClusterId, v.configs.name)),
|
||||
Utils.request(api.isConnectorExist(connectClusterId, v.config.name)),
|
||||
Utils.request(api.getConnectorPlugins(connectClusterId)),
|
||||
]).then(
|
||||
([data, plugins]: [any, ConnectorPlugin[]]) => {
|
||||
return data?.exist
|
||||
? Promise.reject('name 与已有 Connector 重复')
|
||||
: plugins.every((plugin) => plugin.className !== v.configs['connector.class'])
|
||||
: plugins.every((plugin) => plugin.className !== v.config['connector.class'])
|
||||
? Promise.reject('该 connectCluster 下不存在 connector.class 项配置的插件')
|
||||
: Promise.resolve();
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import SmallChart from '@src/components/SmallChart';
|
||||
import TagsWithHide from '@src/components/TagsWithHide';
|
||||
import { Button, Tag, Tooltip, Utils, Popconfirm } from 'knowdesign';
|
||||
import { Button, Tag, Tooltip, Utils, Popconfirm, AppContainer } from 'knowdesign';
|
||||
import React from 'react';
|
||||
import Delete from './Delete';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
export const defaultPagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
@@ -93,7 +94,8 @@ const renderLine = (record: any, metricName: string) => {
|
||||
};
|
||||
|
||||
export const getConnectorsColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Connector Name',
|
||||
dataIndex: 'connectorName',
|
||||
@@ -213,7 +215,10 @@ export const getConnectorsColumns = (arg?: any) => {
|
||||
return t && t.length > 0 ? <TagsWithHide placement="bottom" list={t} expandTagContent={(num: any) => `共有${num}个`} /> : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (global.hasPermission) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
dataIndex: 'options',
|
||||
key: 'options',
|
||||
@@ -224,20 +229,24 @@ export const getConnectorsColumns = (arg?: any) => {
|
||||
render: (_t: any, r: any) => {
|
||||
return (
|
||||
<div>
|
||||
<Popconfirm
|
||||
title="是否重启当前任务?"
|
||||
onConfirm={() => arg?.optionConnect(r, 'restart')}
|
||||
// onCancel={cancel}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
overlayClassName="connect-popconfirm"
|
||||
>
|
||||
<Button key="restart" type="link" size="small">
|
||||
重启
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
{global.hasPermission(ClustersPermissionMap.CONNECTOR_RESTART) ? (
|
||||
<Popconfirm
|
||||
title="是否重启当前任务?"
|
||||
onConfirm={() => arg?.optionConnect(r, 'restart')}
|
||||
// onCancel={cancel}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
overlayClassName="connect-popconfirm"
|
||||
>
|
||||
<Button key="restart" type="link" size="small">
|
||||
重启
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{(r.state === 'RUNNING' || r.state === 'PAUSED') && (
|
||||
{global.hasPermission(ClustersPermissionMap.CONNECTOR_STOP_RESUME) && (r.state === 'RUNNING' || r.state === 'PAUSED') && (
|
||||
<Popconfirm
|
||||
title={`是否${r.state === 'RUNNING' ? '暂停' : '继续'}当前任务?`}
|
||||
onConfirm={() => arg?.optionConnect(r, r.state === 'RUNNING' ? 'stop' : 'resume')}
|
||||
@@ -252,16 +261,24 @@ export const getConnectorsColumns = (arg?: any) => {
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
{global.hasPermission(ClustersPermissionMap.CONNECTOR_CHANGE_CONFIG) ? (
|
||||
<Button type="link" size="small" onClick={() => arg?.editConnector(r)}>
|
||||
编辑
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Button type="link" size="small" onClick={() => arg?.editConnector(r)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Delete record={r} onConfirm={arg?.deleteTesk}></Delete>
|
||||
{global.hasPermission(ClustersPermissionMap.CONNECTOR_DELETE) ? (
|
||||
<Delete record={r} onConfirm={arg?.deleteTesk}></Delete>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
};
|
||||
|
||||
@@ -298,6 +315,7 @@ export const getWorkersColumns = (arg?: any) => {
|
||||
|
||||
// Detail
|
||||
export const getConnectorsDetailColumns = (arg?: any) => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const columns = [
|
||||
{
|
||||
title: 'Task ID',
|
||||
@@ -346,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>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import notification from '@src/components/Notification';
|
||||
import './index.less';
|
||||
import AddConnectorUseJSON from './AddConnectorUseJSON';
|
||||
import HasConnector from './HasConnector';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
const { request } = Utils;
|
||||
|
||||
const rateMap: any = {
|
||||
@@ -174,21 +175,25 @@ const Connectors: React.FC = () => {
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
<span className="add-connect">
|
||||
<Button
|
||||
className="add-connect-btn"
|
||||
icon={<IconFont type="icon-jiahao" />}
|
||||
type="primary"
|
||||
onClick={() => addConnectorRef.current?.onOpen('create', addConnectorJsonRef.current)}
|
||||
>
|
||||
新增Connector
|
||||
</Button>
|
||||
<Dropdown overlayClassName="add-connect-dropdown-menu" overlay={menu}>
|
||||
<Button className="add-connect-json" type="primary">
|
||||
<IconFont type="icon-guanwangxiala" />
|
||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CONNECTOR_ADD) ? (
|
||||
<span className="add-connect">
|
||||
<Button
|
||||
className="add-connect-btn"
|
||||
icon={<IconFont type="icon-jiahao" />}
|
||||
type="primary"
|
||||
onClick={() => addConnectorRef.current?.onOpen('create', addConnectorJsonRef.current)}
|
||||
>
|
||||
新增Connector
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</span>
|
||||
<Dropdown overlayClassName="add-connect-dropdown-menu" overlay={menu}>
|
||||
<Button className="add-connect-json" type="primary">
|
||||
<IconFont type="icon-guanwangxiala" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ProTable
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Button, Form, Input, Modal, Utils } from 'knowdesign';
|
||||
import notification from '@src/components/Notification';
|
||||
import Api from '@src/api/index';
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export default (props: { record: any; onConfirm?: () => void }) => {
|
||||
const { record, onConfirm } = props;
|
||||
const routeParams = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
const [form] = Form.useForm();
|
||||
const [delDialogVisible, setDelDialogVisble] = useState(false);
|
||||
const handleDelOk = () => {
|
||||
form.validateFields().then((e) => {
|
||||
const formVal = form.getFieldsValue();
|
||||
formVal.clusterPhyId = Number(routeParams.clusterId);
|
||||
formVal.deleteType = 0;
|
||||
Utils.delete(Api.deleteGroupOffset(), { data: formVal }).then((res: any) => {
|
||||
if (res === null) {
|
||||
notification.success({
|
||||
message: '删除消费组成功',
|
||||
});
|
||||
setDelDialogVisble(false);
|
||||
onConfirm && onConfirm();
|
||||
} else {
|
||||
notification.error({
|
||||
message: '删除消费组失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
style={{ paddingLeft: 0 }}
|
||||
type="link"
|
||||
onClick={(_) => {
|
||||
setDelDialogVisble(true);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Modal
|
||||
className="custom-modal"
|
||||
title="确定要删除此Topic吗?"
|
||||
centered={true}
|
||||
visible={delDialogVisible}
|
||||
wrapClassName="del-topic-modal"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
onOk={handleDelOk}
|
||||
onCancel={(_) => {
|
||||
setDelDialogVisble(false);
|
||||
}}
|
||||
okText="删除"
|
||||
okButtonProps={{
|
||||
danger: true,
|
||||
size: 'small',
|
||||
style: {
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px',
|
||||
},
|
||||
}}
|
||||
cancelButtonProps={{
|
||||
size: 'small',
|
||||
style: {
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* <div className="tip-info">
|
||||
<IconFont type="icon-warning-circle"></IconFont>
|
||||
<span>会删除Topic的全部消息数据和ACL权限!请再次输入Topic名称进行确认!</span>
|
||||
</div> */}
|
||||
<Form form={form} labelCol={{ span: 5 }} style={{ marginTop: 18 }}>
|
||||
<Form.Item label="TopicName">{record.name}</Form.Item>
|
||||
<Form.Item
|
||||
name="groupName"
|
||||
label="GroupName"
|
||||
rules={[
|
||||
// { required: true },
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('请输入Group名称'));
|
||||
} else if (value !== record.name) {
|
||||
return Promise.reject(new Error('请输入正确的Group名称'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入" size="small"></Input>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { Drawer, ProTable, Utils } from 'knowdesign';
|
||||
import { Button, Space, Divider, Drawer, ProTable, Utils, notification } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '@src/api/index';
|
||||
import { defaultPagination, hashDataParse } from '@src/constants/common';
|
||||
import { getGtoupTopicColumns } from './config';
|
||||
import { ExpandedRow } from './ExpandedRow';
|
||||
import ResetOffsetDrawer from './ResetOffsetDrawer';
|
||||
import { useForceRefresh } from '@src/components/utils';
|
||||
const { request } = Utils;
|
||||
|
||||
export interface MetricLine {
|
||||
@@ -63,6 +64,7 @@ const GroupDetail = (props: any) => {
|
||||
const [openKeys, setOpenKeys] = useState();
|
||||
const [resetOffsetVisible, setResetOffsetVisible] = useState(false);
|
||||
const [resetOffsetArg, setResetOffsetArg] = useState({});
|
||||
const [refreshKey, forceRefresh] = useForceRefresh();
|
||||
|
||||
const genData = async ({ pageNo, pageSize, groupName }: any) => {
|
||||
if (urlParams?.clusterId === undefined) return;
|
||||
@@ -110,6 +112,23 @@ const GroupDetail = (props: any) => {
|
||||
groupName: record?.groupName,
|
||||
});
|
||||
};
|
||||
// 删除消费组Topic
|
||||
const deleteOffset = (record: any) => {
|
||||
const params = {
|
||||
clusterPhyId: +urlParams?.clusterId,
|
||||
deleteType: 1, // 0:group纬度,1:Topic纬度,2:Partition纬度
|
||||
groupName: record.groupName,
|
||||
topicName: record.topicName,
|
||||
};
|
||||
Utils.delete(API.deleteGroupOffset(), { data: params }).then((data: any) => {
|
||||
if (data === null) {
|
||||
notification.success({
|
||||
message: '删除Topic成功!',
|
||||
});
|
||||
genData({ pageNo: 1, pageSize: pagination.pageSize, groupName: hashData.groupName });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter, groupName: hashData.groupName });
|
||||
@@ -160,7 +179,7 @@ const GroupDetail = (props: any) => {
|
||||
// // 获取Consumer列表 表格模式
|
||||
// getTopicGroupMetric(hashData);
|
||||
// });
|
||||
}, [hashDataParse(location.hash).groupName]);
|
||||
}, [hashDataParse(location.hash).groupName, refreshKey]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@@ -182,6 +201,14 @@ const GroupDetail = (props: any) => {
|
||||
// <Divider type="vertical" />
|
||||
// </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
|
||||
showQueryForm={false}
|
||||
@@ -189,7 +216,7 @@ const GroupDetail = (props: any) => {
|
||||
showHeader: false,
|
||||
rowKey: 'key',
|
||||
loading: loading,
|
||||
columns: getGtoupTopicColumns({ resetOffset }),
|
||||
columns: getGtoupTopicColumns({ resetOffset, deleteOffset }),
|
||||
dataSource: topicData,
|
||||
paginationProps: { ...pagination },
|
||||
// noPagination: true,
|
||||
@@ -209,6 +236,7 @@ const GroupDetail = (props: any) => {
|
||||
chartData={chartData}
|
||||
groupName={hashDataParse(location.hash).groupName}
|
||||
loading={loadingObj}
|
||||
refreshKey={refreshKey}
|
||||
/>
|
||||
),
|
||||
// expandedRowRender,
|
||||
@@ -241,7 +269,12 @@ const GroupDetail = (props: any) => {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ResetOffsetDrawer visible={resetOffsetVisible} setVisible={setResetOffsetVisible} record={resetOffsetArg}></ResetOffsetDrawer>
|
||||
<ResetOffsetDrawer
|
||||
visible={resetOffsetVisible}
|
||||
setVisible={setResetOffsetVisible}
|
||||
record={resetOffsetArg}
|
||||
resetOffsetFn={forceRefresh}
|
||||
></ResetOffsetDrawer>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ const metricWithType = [
|
||||
{ metricName: 'Lag', metricType: 102 },
|
||||
];
|
||||
|
||||
export const ExpandedRow: any = ({ record, groupName }: any) => {
|
||||
export const ExpandedRow: any = ({ record, groupName, refreshKey }: any) => {
|
||||
const params: any = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
@@ -193,7 +193,7 @@ export const ExpandedRow: any = ({ record, groupName }: any) => {
|
||||
endTime: timeRange[1],
|
||||
topNu: 0,
|
||||
};
|
||||
Utils.post(API.getTopicGroupMetricHistory(clusterId), params).then((data: Array<MetricData>) => {
|
||||
Utils.post(API.getTopicGroupMetricHistory(clusterId), params, { timeout: 300000 }).then((data: Array<MetricData>) => {
|
||||
// ! 替换接口返回
|
||||
setAllGroupMetricsData(data);
|
||||
});
|
||||
@@ -210,10 +210,6 @@ export const ExpandedRow: any = ({ record, groupName }: any) => {
|
||||
getTopicGroupMetric({ pagination, sorter });
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// getTopicGroupMetric();
|
||||
// }, [sortObj]);
|
||||
|
||||
useEffect(() => {
|
||||
const hashData = hashDataParse(location.hash);
|
||||
// if (!hashData.groupName) return;
|
||||
@@ -242,7 +238,7 @@ export const ExpandedRow: any = ({ record, groupName }: any) => {
|
||||
// 获取Consumer列表 表格模式
|
||||
getTopicGroupMetric({});
|
||||
});
|
||||
}, [hashDataParse(location.hash).groupName]);
|
||||
}, [hashDataParse(location.hash).groupName, refreshKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (partitionList.length === 0) return;
|
||||
|
||||
@@ -44,7 +44,7 @@ const CustomSelectResetTime = (props: { value?: string; onChange?: (val: Number
|
||||
};
|
||||
|
||||
export default (props: any) => {
|
||||
const { record, visible, setVisible } = props;
|
||||
const { record, visible, setVisible, resetOffsetFn } = props;
|
||||
const routeParams = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
@@ -106,6 +106,8 @@ export default (props: any) => {
|
||||
message: '重置offset成功',
|
||||
});
|
||||
setVisible(false);
|
||||
// 发布重置offset成功的消息
|
||||
resetOffsetFn();
|
||||
} else {
|
||||
notification.error({
|
||||
message: '重置offset失败',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import React from 'react';
|
||||
import { AppContainer } from 'knowdesign';
|
||||
import { AppContainer, Button, Popconfirm } from 'knowdesign';
|
||||
import TagsWithHide from '@src/components/TagsWithHide';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import Delete from './Delete';
|
||||
|
||||
export const runningStatusEnum: any = {
|
||||
1: 'Doing',
|
||||
@@ -21,7 +22,8 @@ export const defaultPagination = {
|
||||
};
|
||||
|
||||
export const getGroupColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'ConsumerGroup',
|
||||
dataIndex: 'name',
|
||||
@@ -63,6 +65,23 @@ export const getGroupColumns = (arg?: any) => {
|
||||
render: (t: number) => (t ? t.toLocaleString() : '-'),
|
||||
},
|
||||
];
|
||||
if (global.hasPermission && global.hasPermission(ClustersPermissionMap.GROUP_DELETE)) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
dataIndex: 'options',
|
||||
key: 'options',
|
||||
width: 200,
|
||||
filterTitle: true,
|
||||
fixed: 'right',
|
||||
render: (_t: any, r: any) => {
|
||||
return (
|
||||
<div>
|
||||
<Delete record={r} onConfirm={arg?.deleteTesk}></Delete>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
};
|
||||
|
||||
@@ -98,16 +117,33 @@ export const getGtoupTopicColumns = (arg?: any) => {
|
||||
render: (t: number) => (t ? t.toLocaleString() : '-'),
|
||||
},
|
||||
];
|
||||
if (global.hasPermission && global.hasPermission(ClustersPermissionMap.CONSUMERS_RESET_OFFSET)) {
|
||||
if (global.hasPermission) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
dataIndex: 'desc',
|
||||
key: 'desc',
|
||||
width: 150,
|
||||
width: 200,
|
||||
render: (value: any, record: any) => {
|
||||
return (
|
||||
<div>
|
||||
<a onClick={() => arg.resetOffset(record)}>重置Offset</a>
|
||||
{global.hasPermission(ClustersPermissionMap.CONSUMERS_RESET_OFFSET) ? (
|
||||
<a onClick={() => arg.resetOffset(record)}>重置Offset</a>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{global.hasPermission(ClustersPermissionMap.GROUP_TOPIC_DELETE) ? (
|
||||
<Popconfirm
|
||||
placement="top"
|
||||
title={`是否要删除当前Topic?`}
|
||||
onConfirm={() => arg.deleteOffset(record)}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
>
|
||||
<Button type="link">删除</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -58,6 +58,11 @@ const BrokerList: React.FC = (props: any) => {
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
|
||||
};
|
||||
|
||||
// 删除Group
|
||||
const deleteTesk = () => {
|
||||
genData({ pageNo: 1, pageSize: pagination.pageSize });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
genData({
|
||||
pageNo: 1,
|
||||
@@ -115,7 +120,7 @@ const BrokerList: React.FC = (props: any) => {
|
||||
showHeader: false,
|
||||
rowKey: 'group_list',
|
||||
loading: loading,
|
||||
columns: getGroupColumns(),
|
||||
columns: getGroupColumns(deleteTesk),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -522,28 +522,22 @@ const ConnectorForm = (props: {
|
||||
const params = {
|
||||
...values,
|
||||
id: initFieldsValue?.id,
|
||||
jmxProperties: values.jmxProperties ? `{ "jmxProperties": "${values.jmxProperties}" }` : undefined,
|
||||
jmxProperties: values.jmxProperties ? `{ "jmxPort": "${values.jmxProperties}" }` : undefined,
|
||||
};
|
||||
Utils.put(api.batchConnectClusters, [params])
|
||||
.then((res) => {
|
||||
// setSelectedTabKey(undefined);
|
||||
getConnectClustersList();
|
||||
notification.success({
|
||||
message: '修改Connect集群成功',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
notification.success({
|
||||
message: '修改Connect集群失败',
|
||||
});
|
||||
Utils.put(api.batchConnectClusters, [params]).then((res) => {
|
||||
// setSelectedTabKey(undefined);
|
||||
getConnectClustersList();
|
||||
notification.success({
|
||||
message: '修改Connect集群成功',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setSelectedTabKey(undefined);
|
||||
try {
|
||||
const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {};
|
||||
form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxProperties });
|
||||
form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxPort });
|
||||
} catch {
|
||||
form.setFieldsValue({ ...initFieldsValue });
|
||||
}
|
||||
@@ -552,7 +546,7 @@ const ConnectorForm = (props: {
|
||||
useLayoutEffect(() => {
|
||||
try {
|
||||
const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {};
|
||||
form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxProperties });
|
||||
form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxPort });
|
||||
} catch {
|
||||
form.setFieldsValue({ ...initFieldsValue });
|
||||
}
|
||||
|
||||
@@ -132,16 +132,35 @@ const AddDrawer = forwardRef((_, ref) => {
|
||||
form.validateFields().then((formData) => {
|
||||
const submitData = [];
|
||||
const { configType, principle, kafkaUser } = formData;
|
||||
|
||||
if (configType === 'custom') {
|
||||
// 1. 自定义权限
|
||||
const { resourceType, resourcePatternType, aclPermissionType, aclOperation, aclClientHost } = formData;
|
||||
// TODO: 需要和后端联调
|
||||
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,
|
||||
@@ -281,6 +300,42 @@ const AddDrawer = forwardRef((_, ref) => {
|
||||
</Form.Item>
|
||||
<Form.Item dependencies={['configType']} style={{ marginBottom: 0 }}>
|
||||
{({ 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 { type } = props;
|
||||
const UpperCaseType = type[0].toUpperCase() + type.slice(1);
|
||||
@@ -311,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>
|
||||
@@ -363,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 不能为空' }]}
|
||||
@@ -373,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"
|
||||
@@ -388,6 +449,29 @@ const AddDrawer = forwardRef((_, ref) => {
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item dependencies={['resourceType']}>
|
||||
{({ getFieldValue }) => {
|
||||
const type = getFieldValue('resourceType');
|
||||
if (type === ACL_RESOURCE_TYPE['Cluster']) {
|
||||
//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['TransactionalId']) {
|
||||
return <PatternTypeFormItems type="transactionalId" />;
|
||||
} 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 }}>
|
||||
{({ getFieldValue }) => {
|
||||
form.resetFields(['aclOperation']);
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -23,7 +23,7 @@ import api from '@src/api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { regKafkaPassword } from '@src/constants/reg';
|
||||
import { tableHeaderPrefix } from '@src/constants/common';
|
||||
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
export const randomString = (len = 32, chars = 'abcdefghijklmnopqrstuvwxyz1234567890'): string => {
|
||||
const maxPos = chars.length;
|
||||
let str = '';
|
||||
@@ -85,7 +85,7 @@ const PasswordContent = (props: { clusterId: string; name: string }) => {
|
||||
const { clusterId, name } = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pw, setPw] = useState(initialShowPassword);
|
||||
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const switchPwStatus = () => {
|
||||
if (!loading) {
|
||||
setLoading(true);
|
||||
@@ -113,9 +113,13 @@ const PasswordContent = (props: { clusterId: string; name: string }) => {
|
||||
<Tooltip title={pw} placement="bottom">
|
||||
<div style={{ maxWidth: '80%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{pw}</div>
|
||||
</Tooltip>
|
||||
<span style={{ marginLeft: 6 }} onClick={switchPwStatus}>
|
||||
{loading ? <LoadingOutlined /> : pw === initialShowPassword ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
</span>
|
||||
{global.hasPermission(ClustersPermissionMap.SECURITY_USER_VIEW_PASSWORD) ? (
|
||||
<span style={{ marginLeft: 6 }} onClick={switchPwStatus}>
|
||||
{loading ? <LoadingOutlined /> : pw === initialShowPassword ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -323,7 +327,7 @@ const SecurityUsers = (): JSX.Element => {
|
||||
};
|
||||
|
||||
const columns = () => {
|
||||
const baseColumns = [
|
||||
const baseColumns: any = [
|
||||
{
|
||||
title: 'KafkaUser',
|
||||
dataIndex: 'name',
|
||||
@@ -348,30 +352,39 @@ const SecurityUsers = (): JSX.Element => {
|
||||
return <PasswordContent clusterId={clusterId} name={record.name} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
];
|
||||
if (global.hasPermission) {
|
||||
baseColumns.push({
|
||||
title: '操作',
|
||||
dataIndex: '',
|
||||
width: 240,
|
||||
render(record: UsersProps) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
style={{ paddingLeft: 0 }}
|
||||
onClick={() => editDrawerRef.current.onOpen(true, UsersOperate.ChangePassword, getKafkaUserList, record)}
|
||||
>
|
||||
修改密码
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => onDelete(record)}>
|
||||
删除
|
||||
</Button>
|
||||
{global.hasPermission(ClustersPermissionMap.SECURITY_USER_EDIT_PASSWORD) ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
style={{ paddingLeft: 0 }}
|
||||
onClick={() => editDrawerRef.current.onOpen(true, UsersOperate.ChangePassword, getKafkaUserList, record)}
|
||||
>
|
||||
修改密码
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{global.hasPermission(ClustersPermissionMap.SECURITY_USER_DELETE) ? (
|
||||
<Button type="link" size="small" onClick={() => onDelete(record)}>
|
||||
删除
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
});
|
||||
}
|
||||
return baseColumns;
|
||||
};
|
||||
|
||||
@@ -454,13 +467,17 @@ const SecurityUsers = (): JSX.Element => {
|
||||
setSearchKeywordsInput(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
// icon={<PlusOutlined />}
|
||||
onClick={() => editDrawerRef.current.onOpen(true, UsersOperate.Add, getKafkaUserList)}
|
||||
>
|
||||
新增KafkaUser
|
||||
</Button>
|
||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.SECURITY_USER_ADD) ? (
|
||||
<Button
|
||||
type="primary"
|
||||
// icon={<PlusOutlined />}
|
||||
onClick={() => editDrawerRef.current.onOpen(true, UsersOperate.Add, getKafkaUserList)}
|
||||
>
|
||||
新增KafkaUser
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useParams } from 'react-router-dom';
|
||||
import TagsWithHide from '@src/components/TagsWithHide';
|
||||
import SwitchTab from '@src/components/SwitchTab';
|
||||
import RenderEmpty from '@src/components/RenderEmpty';
|
||||
import { useForceRefresh } from '@src/components/utils';
|
||||
|
||||
interface PropsType {
|
||||
hashData: any;
|
||||
@@ -401,11 +402,18 @@ export default (props: PropsType) => {
|
||||
const { hashData } = props;
|
||||
const [showMode, setShowMode] = useState<string>('card');
|
||||
|
||||
const [refreshKey, forceRefresh] = useForceRefresh();
|
||||
return (
|
||||
<>
|
||||
<div className="brokers-tab-container">
|
||||
<div className="brokers-tab-container" key={`${refreshKey}`}>
|
||||
<div className="overview">
|
||||
<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} />
|
||||
</div>
|
||||
<div className="cases-box">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import ResetOffsetDrawer from './ResetOffsetDrawer';
|
||||
import SwitchTab from '@src/components/SwitchTab';
|
||||
import ContentWithCopy from '@src/components/CopyContent';
|
||||
import PubSub from "pubsub-js";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -335,6 +336,11 @@ export default (props: any) => {
|
||||
});
|
||||
}, [visible]);
|
||||
|
||||
// 订阅重置offset成功的消息
|
||||
PubSub.subscribe('TopicDetail-ResetOffset', function(message, data){
|
||||
getTopicGroupMetric({hashData: data});
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (partitionList.length === 0) return;
|
||||
getTopicGroupMetricHistory(partitionList, hashData);
|
||||
|
||||
@@ -4,6 +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";
|
||||
|
||||
const CustomSelectResetTime = (props: { value?: string; onChange?: (val: Number | String) => void }) => {
|
||||
const { value, onChange } = props;
|
||||
@@ -106,6 +107,13 @@ export default (props: any) => {
|
||||
message: '重置offset成功',
|
||||
});
|
||||
setResetOffsetVisible(false);
|
||||
// 发布重置offset成功的消息
|
||||
PubSub.publish('TopicDetail-ResetOffset',
|
||||
{
|
||||
groupName: record.groupName,
|
||||
topicName: record.topicName
|
||||
}
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
message: '重置offset失败',
|
||||
|
||||
@@ -81,7 +81,8 @@ export const getTopicMessagesColmns = () => {
|
||||
title: 'Offset',
|
||||
dataIndex: 'offset',
|
||||
key: 'offset',
|
||||
render: (t: number) => (t ? t.toLocaleString() : '-'),
|
||||
sorter: true,
|
||||
render: (t: number) => (+t || +t === 0 ? t.toLocaleString() : '-'), // TODO: 千分位展示
|
||||
},
|
||||
{
|
||||
title: 'Timestamp',
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.info-box {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
|
||||
@@ -15,9 +15,21 @@ import Replicator from './Replicator';
|
||||
import './index.less';
|
||||
import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck';
|
||||
import { hashDataParse } from '@src/constants/common';
|
||||
import { useForceRefresh } from '@src/components/utils';
|
||||
|
||||
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 = {
|
||||
// eslint-disable-next-line react/display-name
|
||||
// ['Partitions']: (arg: any) => {
|
||||
@@ -70,17 +82,20 @@ const OperationsSlot: any = {
|
||||
// eslint-disable-next-line react/display-name
|
||||
['ConsumerGroups']: (arg: any) => {
|
||||
return (
|
||||
<SearchInput
|
||||
onSearch={arg.setSearchKeywords}
|
||||
attrs={{
|
||||
value: arg.searchValue,
|
||||
onChange: arg.setSearchValue,
|
||||
placeholder: '请输入Consumer Group',
|
||||
size: 'small',
|
||||
style: { width: '210px', marginRight: '2px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<Reload {...arg} />
|
||||
<SearchInput
|
||||
onSearch={arg.setSearchKeywords}
|
||||
attrs={{
|
||||
value: arg.searchValue,
|
||||
onChange: arg.setSearchValue,
|
||||
placeholder: '请输入Consumer Group',
|
||||
size: 'small',
|
||||
style: { width: '210px', marginRight: '2px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -94,6 +109,7 @@ const TopicDetail = (props: any) => {
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [hashData, setHashData] = useState<any>({});
|
||||
const [refreshKey, forceRefresh] = useForceRefresh();
|
||||
|
||||
const callback = (key: any) => {
|
||||
setSearchValue('');
|
||||
@@ -184,7 +200,7 @@ const TopicDetail = (props: any) => {
|
||||
onChange={callback}
|
||||
tabBarExtraContent={
|
||||
OperationsSlot[positionType] &&
|
||||
OperationsSlot[positionType]({ ...props, setSearchKeywords, setSearchValue, searchValue, positionType })
|
||||
OperationsSlot[positionType]({ ...props, setSearchKeywords, setSearchValue, searchValue, positionType, forceRefresh })
|
||||
}
|
||||
destroyInactiveTabPane
|
||||
>
|
||||
@@ -196,7 +212,7 @@ const TopicDetail = (props: any) => {
|
||||
</TabPane>
|
||||
<TabPane tab="ConsumerGroups" key="ConsumerGroups">
|
||||
{positionType === 'ConsumerGroups' && (
|
||||
<Consumers searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} />
|
||||
<Consumers searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} key={`${refreshKey}`} />
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane tab="ACLs" key="ACLs">
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { AppContainer, Input, ProTable, Select, Switch, Tooltip, Utils, Dropdown, Menu, Button, Divider, Tag } from 'knowdesign';
|
||||
import {
|
||||
AppContainer,
|
||||
Input,
|
||||
ProTable,
|
||||
Select,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Utils,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Button,
|
||||
Divider,
|
||||
Tag,
|
||||
Popconfirm,
|
||||
notification,
|
||||
} from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import Create from './Create';
|
||||
import './index.less';
|
||||
@@ -85,6 +100,21 @@ const AutoPage = (props: any) => {
|
||||
setTopicListLoading(false);
|
||||
});
|
||||
};
|
||||
const deleteTopicData = (record: any) => {
|
||||
console.log(record, 'record');
|
||||
const params = {
|
||||
clusterId: Number(routeParams.clusterId),
|
||||
topicName: record.topicName,
|
||||
};
|
||||
Utils.post(Api.deleteTopicData(), params).then((data: any) => {
|
||||
if (data === null) {
|
||||
notification.success({
|
||||
message: '清除数据成功',
|
||||
});
|
||||
getTopicsList();
|
||||
}
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
getTopicsList();
|
||||
}, [sortObj, showInternalTopics, searchKeywords, pageIndex, pageSize]);
|
||||
@@ -247,7 +277,7 @@ const AutoPage = (props: any) => {
|
||||
dataIndex: 'desc',
|
||||
key: 'desc',
|
||||
fixed: 'right',
|
||||
width: 140,
|
||||
width: 200,
|
||||
render: (value: any, record: any) => {
|
||||
return (
|
||||
<div className="operation-list">
|
||||
@@ -257,6 +287,19 @@ const AutoPage = (props: any) => {
|
||||
<></>
|
||||
)}
|
||||
{global.hasPermission(ClustersPermissionMap.TOPIC_DEL) ? <Delete record={record} onConfirm={getTopicsList}></Delete> : <></>}
|
||||
{global.hasPermission(ClustersPermissionMap.TOPIC_DEL) ? ( // TODO:替换为清除数据的权限
|
||||
<Popconfirm
|
||||
placement="topRight"
|
||||
title={`是否要清空当前Topic的数据?`}
|
||||
onConfirm={() => deleteTopicData(record)}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
>
|
||||
<Button type="link">清除数据</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user