mirror of
https://github.com/didi/KnowStreaming.git
synced 2025-12-24 11:52:08 +08:00
1
km-console/.gitignore
vendored
1
km-console/.gitignore
vendored
@@ -9,6 +9,5 @@ build/
|
|||||||
coverage
|
coverage
|
||||||
versions/
|
versions/
|
||||||
debug.log
|
debug.log
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
target
|
target
|
||||||
@@ -1,43 +1,60 @@
|
|||||||
## 安装项目依赖
|
## 前提
|
||||||
|
|
||||||
- 安装 lerna
|
正常情况下,您应该通过 [本地源码启动手册](https://github.com/didi/KnowStreaming/blob/master/docs/dev_guide/%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%90%AF%E5%8A%A8%E6%89%8B%E5%86%8C.md) 来打包工程。如果您有需要在本地独立启动前端服务,请参考以下手册。
|
||||||
|
|
||||||
|
在进行以下的步骤之前,首先确保您已经安装了 `node`。如已安装,可以通过在终端执行 `node -v` 来获取到 node 版本,项目推荐使用 `node v12` 版本运行。
|
||||||
|
|
||||||
|
另外,`windows` 用户请在 `git bash` 下运行下面的命令。
|
||||||
|
|
||||||
|
## 一、安装项目依赖(必须)
|
||||||
|
|
||||||
|
1. 安装 lerna(可选,安装后可以直接通过 lerna 的全局指令管理项目,如果不了解 lerna 可以不安装)
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install -g lerna
|
npm install -g lerna
|
||||||
```
|
```
|
||||||
|
|
||||||
- 安装项目依赖
|
2. 安装项目依赖
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run i
|
npm run i
|
||||||
```
|
```
|
||||||
|
|
||||||
## 启动项目
|
我们默认保留了 `package-lock.json` 文件,以防止可能的依赖包自动升级导致的问题。依赖默认会通过 `https://registry.npmjs.org` 服务下载,如果您无法连通该服务器,请删除当前目录及 `packages/*` 子目录下的 `package-lock.json` 后,在当前目录下使用 `node v12` 版本执行命令 `npm run i`。
|
||||||
|
|
||||||
|
## 二、启动项目
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run start
|
npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
### 环境信息
|
该指令会启动 `packages` 目录下的所有应用,如果需要单独启动应用,其查看下方 QA。
|
||||||
|
|
||||||
http://localhost:port
|
多集群管理应用会启动在 http://localhost:8000,系统管理应用会占用 http://localhost:8001。
|
||||||
|
请确认 `8000` 和 `8001` 端口没有被其他应用占用。
|
||||||
|
|
||||||
## 构建项目
|
后端本地服务启动在 http://localhost:8080,请求通过 webpack dev server 代理访问 8080 端口,需要启动后端服务后才能正常请求接口。
|
||||||
|
|
||||||
|
如果启动失败,可以参见另外一种本地启动方式 [本地源码启动手册](https://github.com/didi/KnowStreaming/blob/master/docs/dev_guide/%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%90%AF%E5%8A%A8%E6%89%8B%E5%86%8C.md)
|
||||||
|
|
||||||
|
## 三、构建项目
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
项目构建成功后,会存放到 km-rest/src/main/resources/tamplates 目录下。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
- packages
|
- packages
|
||||||
- layout-clusters-fe: 基座应用 & 多集群管理
|
- layout-clusters-fe: 基座应用 & 多集群管理(其余应用启动需要首先启动该应用)
|
||||||
- config-manager-fe: 子应用 - 系统管理
|
- config-manager-fe: 子应用 - 系统管理
|
||||||
- tool: 启动 & 打包脚本
|
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
|
|
||||||
Q: 执行 `npm run start` 时看不到应用构建和热加载过程?
|
Q: 在 `km-console` 目录下执行 `npm run start` 时看不到应用构建和热加载过程?如何启动单个应用?
|
||||||
|
|
||||||
A: 需要到具体的应用中执行 `npm run start`,例如 `cd packages/layout-clusters-fe` 后,执行 `npm run start`。
|
A: 需要到具体的应用中执行 `npm run start`,例如 `cd packages/layout-clusters-fe` 后,执行 `npm run start`。
|
||||||
|
|||||||
8567
km-console/package-lock.json
generated
Normal file
8567
km-console/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,15 +17,15 @@
|
|||||||
"eslint-plugin-react": "7.22.0",
|
"eslint-plugin-react": "7.22.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"husky": "4.3.7",
|
"husky": "4.3.7",
|
||||||
"lerna": "^4.0.0",
|
"lerna": "^5.5.0",
|
||||||
"lint-staged": "10.5.3",
|
"lint-staged": "10.5.3",
|
||||||
"prettier": "2.3.2"
|
"prettier": "2.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"i": "npm install && lerna bootstrap",
|
"i": "npm install && lerna bootstrap",
|
||||||
"clean": "rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json",
|
"clean": "rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json",
|
||||||
"start": "sh ./tool/start.sh",
|
"start": "lerna run start",
|
||||||
"build": "sh ./tool/build.sh",
|
"build": "lerna run build",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
|
||||||
"cm": "git add . && cz"
|
"cm": "git add . && cz"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,5 +9,4 @@ build/
|
|||||||
coverage
|
coverage
|
||||||
versions/
|
versions/
|
||||||
debug.log
|
debug.log
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
## 使用说明
|
## 使用说明
|
||||||
|
|
||||||
### 依赖安装:
|
### 依赖安装(如在 km-console 目录下执行 npm run i 安装过依赖,这步可以省略):
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
@@ -12,6 +12,8 @@ npm install
|
|||||||
npm run start
|
npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
该应用为子应用,启动后需要到基座应用中查看(需要启动基座应用),地址为 http://localhost:8000
|
||||||
|
|
||||||
### 构建:
|
### 构建:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
13845
km-console/packages/config-manager-fe/package-lock.json
generated
Normal file
13845
km-console/packages/config-manager-fe/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||||
"start": "cross-env NODE_ENV=development webpack-dev-server",
|
"start": "cross-env NODE_ENV=development webpack-dev-server",
|
||||||
"build": "rm -rf ../../pub/layout & cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-preset-react-app": "^10.0.0",
|
"babel-preset-react-app": "^10.0.0",
|
||||||
|
|||||||
@@ -35,7 +35,16 @@ serviceInstance.interceptors.request.use(
|
|||||||
// 响应拦截
|
// 响应拦截
|
||||||
serviceInstance.interceptors.response.use(
|
serviceInstance.interceptors.response.use(
|
||||||
(config: any) => {
|
(config: any) => {
|
||||||
return config.data;
|
const res: { code: number; message: string; data: any } = config.data;
|
||||||
|
if (res.code !== 0 && res.code !== 200) {
|
||||||
|
const desc = res.message;
|
||||||
|
notification.error({
|
||||||
|
message: desc,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
throw res;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
const config = err.config;
|
const config = err.config;
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ const CheckboxGroupContainer = (props: CheckboxGroupType) => {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox.Group disabled={disabled} style={{ width: '100%' }} value={checkedList} onChange={onCheckedChange}>
|
<Checkbox.Group disabled={disabled} style={{ width: '100%' }} value={checkedList} onChange={onCheckedChange}>
|
||||||
<Row gutter={[34, 10]}>
|
<Row gutter={[10, 10]}>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
return (
|
return (
|
||||||
<Col span={8} key={option.value}>
|
<Col span={8} key={option.value}>
|
||||||
<Checkbox value={option.value} className="checkbox-content-ellipsis">
|
<Checkbox value={option.value} className="checkbox-content-ellipsis">
|
||||||
{option.label}
|
{option.label.replace('Cluster-Load', '')}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
IconFont,
|
IconFont,
|
||||||
} from 'knowdesign';
|
} from 'knowdesign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { CloseOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { defaultPagination } from 'constants/common';
|
import { defaultPagination } from 'constants/common';
|
||||||
import { RoleProps, PermissionNode, AssignUser, RoleOperate, FormItemPermission } from './config';
|
import { RoleProps, PermissionNode, AssignUser, RoleOperate, FormItemPermission } from './config';
|
||||||
import api from 'api';
|
import api from 'api';
|
||||||
@@ -50,11 +50,21 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const globalPermissions = global.permissions;
|
const globalPermissions = global.permissions;
|
||||||
if (globalPermissions && globalPermissions.length) {
|
if (globalPermissions && globalPermissions.length) {
|
||||||
const sysPermissions = globalPermissions.map((sys: PermissionNode) => ({
|
const sysPermissions = globalPermissions.map((sys: PermissionNode) => {
|
||||||
id: sys.id,
|
const result = {
|
||||||
name: sys.permissionName,
|
id: sys.id,
|
||||||
options: sys.childList.map((node) => ({ label: node.permissionName, value: node.id })),
|
name: sys.permissionName,
|
||||||
}));
|
essentialPermission: undefined,
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
result.options = sys.childList.map((node) => {
|
||||||
|
if (node.permissionName === '多集群管理查看' || node.permissionName === '系统管理查看') {
|
||||||
|
result.essentialPermission = { label: node.permissionName, value: node.id };
|
||||||
|
}
|
||||||
|
return { label: node.permissionName, value: node.id };
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
setPermissions(sysPermissions);
|
setPermissions(sysPermissions);
|
||||||
}
|
}
|
||||||
}, [global]);
|
}, [global]);
|
||||||
@@ -77,10 +87,12 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
|
|||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
form.validateFields().then((formData) => {
|
form.validateFields().then((formData) => {
|
||||||
formData.permissionIdList = formData.permissionIdList.filter((l) => l);
|
|
||||||
formData.permissionIdList.forEach((arr, i) => {
|
formData.permissionIdList.forEach((arr, i) => {
|
||||||
// 如果分配的系统下的子权限,自动赋予该系统的权限
|
// 如果分配的系统下的子权限,自动赋予该系统的权限
|
||||||
if (arr !== null && arr.length) {
|
if (!Array.isArray(arr)) {
|
||||||
|
arr = [];
|
||||||
|
}
|
||||||
|
if (arr?.length) {
|
||||||
arr.push(permissions[i].id);
|
arr.push(permissions[i].id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -210,10 +222,20 @@ const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label="分配权限"
|
label="分配权限"
|
||||||
name="permissionIdList"
|
name="permissionIdList"
|
||||||
|
required
|
||||||
rules={[
|
rules={[
|
||||||
() => ({
|
() => ({
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
if (Array.isArray(value) && value.some((item) => !!item?.length)) {
|
if (Array.isArray(value) && value.some((item) => !!item?.length)) {
|
||||||
|
const errs = [];
|
||||||
|
value.forEach((arr, i) => {
|
||||||
|
if (arr?.length && !arr.includes(permissions[i].essentialPermission.value)) {
|
||||||
|
errs.push(`[${permissions[i].essentialPermission.label}]`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (errs.length) {
|
||||||
|
return Promise.reject(`您必须分配 ${errs.join(' 和 ')} 权限`);
|
||||||
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error('请为角色至少分配一项权限'));
|
return Promise.reject(new Error('请为角色至少分配一项权限'));
|
||||||
|
|||||||
@@ -59,5 +59,6 @@ export enum RoleOperate {
|
|||||||
export interface FormItemPermission {
|
export interface FormItemPermission {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
essentialPermission: { label: string; value: number };
|
||||||
options: { label: string; value: number }[];
|
options: { label: string; value: number }[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ build/
|
|||||||
coverage
|
coverage
|
||||||
versions/
|
versions/
|
||||||
debug.log
|
debug.log
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.d1-workspace.json
|
.d1-workspace.json
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## 使用说明
|
## 使用说明
|
||||||
|
|
||||||
### 依赖安装:
|
### 依赖安装(如在 km-console 目录下执行 npm run i 安装过依赖,这步可以省略):
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
@@ -12,6 +12,8 @@ npm install
|
|||||||
npm run start
|
npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
启动后访问地址为 http://localhost:8000
|
||||||
|
|
||||||
### 构建:
|
### 构建:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
14801
km-console/packages/layout-clusters-fe/package-lock.json
generated
Normal file
14801
km-console/packages/layout-clusters-fe/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||||
"start": "cross-env NODE_ENV=development webpack-dev-server",
|
"start": "cross-env NODE_ENV=development webpack-dev-server",
|
||||||
"build": "rm -rf ../../pub/layout & cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const logout = () => {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
});
|
});
|
||||||
|
localStorage.removeItem('userInfo');
|
||||||
};
|
};
|
||||||
|
|
||||||
const LicenseLimitModal = () => {
|
const LicenseLimitModal = () => {
|
||||||
@@ -117,7 +118,7 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
|||||||
<DProLayout.Container
|
<DProLayout.Container
|
||||||
headerProps={{
|
headerProps={{
|
||||||
title: (
|
title: (
|
||||||
<div>
|
<div style={{ cursor: 'pointer' }}>
|
||||||
<img className="header-logo" src={ksLogo} />
|
<img className="header-logo" src={ksLogo} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span style={{ display: 'inline-block', marginRight: '8px' }}>Similar Config</span>
|
<span style={{ display: 'inline-block', marginRight: '8px' }}>Similar Config</span>
|
||||||
<Tooltip overlayClassName="rebalance-tooltip" title="所有broker配置是否一致">
|
<Tooltip overlayClassName="rebalance-tooltip" title="所有Broker配置是否一致">
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +111,7 @@ export default () => {
|
|||||||
];
|
];
|
||||||
setCardData(cordRightMap);
|
setCardData(cordRightMap);
|
||||||
});
|
});
|
||||||
Promise.all([brokerMetric, brokersState]).then((res) => {
|
Promise.all([brokerMetric, brokersState]).finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [routeParams.clusterId]);
|
}, [routeParams.clusterId]);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import CardBar from './index';
|
import CardBar from './index';
|
||||||
import { IconFont, Tag, Utils, Tooltip, Popover } from 'knowdesign';
|
import { IconFont, Tag, Utils, Tooltip, Popover, AppContainer } from 'knowdesign';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
import StateChart from './StateChart';
|
import StateChart from './StateChart';
|
||||||
import ClusterNorms from '@src/pages/LoadRebalance/ClusterNorms';
|
import ClusterNorms from '@src/pages/LoadRebalance/ClusterNorms';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
|
||||||
|
|
||||||
const transUnitTimePro = (ms: number, num = 0) => {
|
const transUnitTimePro = (ms: number, num = 0) => {
|
||||||
if (!ms) return '';
|
if (!ms) return '';
|
||||||
@@ -23,6 +24,7 @@ const transUnitTimePro = (ms: number, num = 0) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LoadRebalanceCardBar = (props: any) => {
|
const LoadRebalanceCardBar = (props: any) => {
|
||||||
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const { clusterId } = useParams<{
|
const { clusterId } = useParams<{
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
}>();
|
}>();
|
||||||
@@ -53,12 +55,14 @@ const LoadRebalanceCardBar = (props: any) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ height: '20px' }}>
|
<div style={{ height: '20px' }}>
|
||||||
<span style={{ display: 'inline-block', marginRight: '8px' }}>State</span>
|
<span style={{ display: 'inline-block', marginRight: '8px' }}>State</span>
|
||||||
<IconFont
|
{global.hasPermission(ClustersPermissionMap.REBALANCE_SETTING) && (
|
||||||
className="cutomIcon-config"
|
<IconFont
|
||||||
style={{ fontSize: '15px' }}
|
className="cutomIcon-config"
|
||||||
onClick={() => setNormsVisible(true)}
|
style={{ fontSize: '15px' }}
|
||||||
type="icon-shezhi"
|
onClick={() => setNormsVisible(true)}
|
||||||
></IconFont>
|
type="icon-shezhi"
|
||||||
|
></IconFont>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||||
import { AppContainer, Button, Drawer, IconFont, message, Spin, Table, SingleChart, Utils, Tooltip } from 'knowdesign';
|
import { AppContainer, Drawer, Spin, Table, SingleChart, Utils, Tooltip } from 'knowdesign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import api, { MetricType } from '@src/api';
|
import api, { MetricType } from '@src/api';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData, getDetailChartConfig } from './config';
|
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData, getDetailChartConfig } from './config';
|
||||||
import { UNIT_MAP } from '@src/constants/chartConfig';
|
import { UNIT_MAP } from '@src/constants/chartConfig';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import RenderEmpty from '../RenderEmpty';
|
||||||
|
|
||||||
interface ChartDetailProps {
|
interface ChartDetailProps {
|
||||||
metricType: MetricType;
|
metricType: MetricType;
|
||||||
metricName: string;
|
metricName: string;
|
||||||
queryLines: string[];
|
queryLines: string[];
|
||||||
onClose: () => void;
|
setSliderRange: (range: string) => void;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
setDisposeChartInstance: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetricTableInfo {
|
interface MetricTableInfo {
|
||||||
@@ -24,6 +26,18 @@ interface MetricTableInfo {
|
|||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ChartInfo {
|
||||||
|
chartInstance?: echarts.ECharts;
|
||||||
|
isLoadingAdditionData?: boolean;
|
||||||
|
isLoadedFullData?: boolean;
|
||||||
|
fullTimeRange?: readonly [number, number];
|
||||||
|
curTimeRange?: readonly [number, number];
|
||||||
|
sliderPos?: readonly [number, number];
|
||||||
|
transformUnit?: [string, number];
|
||||||
|
fullMetricData?: MetricChartDataType;
|
||||||
|
oldDataZoomOption?: any;
|
||||||
|
}
|
||||||
|
|
||||||
interface DataZoomEventProps {
|
interface DataZoomEventProps {
|
||||||
type: 'datazoom';
|
type: 'datazoom';
|
||||||
// 缩放的开始位置的百分比,0 - 100
|
// 缩放的开始位置的百分比,0 - 100
|
||||||
@@ -34,8 +48,6 @@ interface DataZoomEventProps {
|
|||||||
|
|
||||||
// 缩放区默认选中范围比例(0.01~1)
|
// 缩放区默认选中范围比例(0.01~1)
|
||||||
const DATA_ZOOM_DEFAULT_SCALE = 0.25;
|
const DATA_ZOOM_DEFAULT_SCALE = 0.25;
|
||||||
// 选中范围最少展示的时间长度(默认 10 分钟),单位: ms
|
|
||||||
const LEAST_SELECTED_TIME_RANGE = 1 * 60 * 1000;
|
|
||||||
// 单次向服务器请求数据的范围(默认 6 小时,超过后采集频率间隔会变长),单位: ms
|
// 单次向服务器请求数据的范围(默认 6 小时,超过后采集频率间隔会变长),单位: ms
|
||||||
const DEFAULT_REQUEST_TIME_RANGE = 6 * 60 * 60 * 1000;
|
const DEFAULT_REQUEST_TIME_RANGE = 6 * 60 * 60 * 1000;
|
||||||
// 采样间隔,影响前端补点逻辑,单位: ms
|
// 采样间隔,影响前端补点逻辑,单位: ms
|
||||||
@@ -47,70 +59,15 @@ const DEFAULT_ENTER_TIME_RANGE = 2 * 60 * 60 * 1000;
|
|||||||
// 预缓存数据阈值,图表展示数据的开始时间处于前端缓存数据的时间范围的前 40% 时,向服务器请求数据
|
// 预缓存数据阈值,图表展示数据的开始时间处于前端缓存数据的时间范围的前 40% 时,向服务器请求数据
|
||||||
const PRECACHE_THRESHOLD = 0.4;
|
const PRECACHE_THRESHOLD = 0.4;
|
||||||
|
|
||||||
// 表格列
|
|
||||||
const colunms = [
|
|
||||||
{
|
|
||||||
title: 'Host',
|
|
||||||
dataIndex: 'name',
|
|
||||||
width: 200,
|
|
||||||
render(name: string, record: any) {
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<div style={{ width: 8, height: 2, marginRight: 4, background: record.color }}></div>
|
|
||||||
<span>{name}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Avg',
|
|
||||||
dataIndex: 'avg',
|
|
||||||
width: 120,
|
|
||||||
render(num: number) {
|
|
||||||
return num.toFixed(2);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Max',
|
|
||||||
dataIndex: 'max',
|
|
||||||
width: 120,
|
|
||||||
render(num: number, record: any) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<span>{num.toFixed(2)}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Min',
|
|
||||||
dataIndex: 'min',
|
|
||||||
width: 120,
|
|
||||||
render(num: number, record: any) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<span>{num.toFixed(2)}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Latest',
|
|
||||||
dataIndex: 'latest',
|
|
||||||
width: 120,
|
|
||||||
render(latest: number[]) {
|
|
||||||
return `${latest[1].toFixed(2)}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ChartDetail = (props: ChartDetailProps) => {
|
const ChartDetail = (props: ChartDetailProps) => {
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const { clusterId } = useParams<{
|
const { clusterId } = useParams<{
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
}>();
|
}>();
|
||||||
const { metricType, metricName, queryLines, onClose } = props;
|
const { metricType, metricName, queryLines, setSliderRange, setDisposeChartInstance } = props;
|
||||||
|
|
||||||
|
// 初始化拖拽防抖函数
|
||||||
|
const debouncedZoomDrag = useRef(null);
|
||||||
// 存储图表相关的不需要触发渲染的数据,用于计算图表展示状态并进行操作
|
// 存储图表相关的不需要触发渲染的数据,用于计算图表展示状态并进行操作
|
||||||
const chartInfo = useRef(
|
const chartInfo = useRef(
|
||||||
(() => {
|
(() => {
|
||||||
@@ -119,16 +76,16 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
const curTimeRange = [curTime - DEFAULT_ENTER_TIME_RANGE, curTime] as const;
|
const curTimeRange = [curTime - DEFAULT_ENTER_TIME_RANGE, curTime] as const;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chartInstance: undefined as echarts.ECharts,
|
chartInstance: undefined,
|
||||||
|
isLoadingAdditionData: false,
|
||||||
isLoadedFullData: false,
|
isLoadedFullData: false,
|
||||||
fullTimeRange: curTimeRange,
|
fullTimeRange: curTimeRange,
|
||||||
fullMetricData: {} as MetricChartDataType,
|
fullMetricData: {} as MetricChartDataType,
|
||||||
curTimeRange,
|
curTimeRange,
|
||||||
oldDataZoomOption: {} as any,
|
oldDataZoomOption: {},
|
||||||
sliderPos: [0, 0] as readonly [number, number],
|
sliderPos: [0, 0],
|
||||||
sliderRange: '',
|
transformUnit: undefined,
|
||||||
transformUnit: undefined as [string, number],
|
} as ChartInfo;
|
||||||
};
|
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,8 +94,76 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
const [curMetricData, setCurMetricData] = useState<MetricChartDataType>();
|
const [curMetricData, setCurMetricData] = useState<MetricChartDataType>();
|
||||||
// 图表数据的各项计算指标
|
// 图表数据的各项计算指标
|
||||||
const [tableInfo, setTableInfo] = useState<MetricTableInfo[]>([]);
|
const [tableInfo, setTableInfo] = useState<MetricTableInfo[]>([]);
|
||||||
// 选中展示的图表
|
const [linesStatus, setLinesStatus] = useState<{
|
||||||
const [selectedLines, setSelectedLines] = useState<string[]>([]);
|
[lineName: string]: boolean;
|
||||||
|
}>({});
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const colunms = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
title: metricType === MetricType.Broker ? 'Host' : 'Topic',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 200,
|
||||||
|
render(name: string, record: any) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{ width: 8, height: 2, marginRight: 4, background: record.color }}></div>
|
||||||
|
<span>{name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Avg',
|
||||||
|
dataIndex: 'avg',
|
||||||
|
width: 120,
|
||||||
|
render(num: number) {
|
||||||
|
return num.toFixed(2);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Max',
|
||||||
|
dataIndex: 'max',
|
||||||
|
width: 120,
|
||||||
|
render(num: number, record: any) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{num.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Min',
|
||||||
|
dataIndex: 'min',
|
||||||
|
width: 120,
|
||||||
|
render(num: number, record: any) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{num.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Latest',
|
||||||
|
dataIndex: 'latest',
|
||||||
|
width: 120,
|
||||||
|
render(latest: number[]) {
|
||||||
|
return `${latest[1].toFixed(2)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[metricType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateChartInfo = (changedInfo: ChartInfo) => {
|
||||||
|
chartInfo.current = {
|
||||||
|
...chartInfo.current,
|
||||||
|
...changedInfo,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 请求图表数据
|
// 请求图表数据
|
||||||
const getMetricChartData = ([startTime, endTime]: readonly [number, number]) => {
|
const getMetricChartData = ([startTime, endTime]: readonly [number, number]) => {
|
||||||
@@ -175,11 +200,10 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
|
|
||||||
// 如果滑块整体拖动,则只更新拖动后滑块的位(保留小数点后三位是防止低位值的干扰)
|
// 如果滑块整体拖动,则只更新拖动后滑块的位(保留小数点后三位是防止低位值的干扰)
|
||||||
if (oldScale.toFixed(3) === newScale.toFixed(3)) {
|
if (oldScale.toFixed(3) === newScale.toFixed(3)) {
|
||||||
chartInfo.current = {
|
updateChartInfo({
|
||||||
...chartInfo.current,
|
|
||||||
sliderPos: [newStartSliderPos, newEndSliderPos],
|
sliderPos: [newStartSliderPos, newEndSliderPos],
|
||||||
oldDataZoomOption: newDataZoomOption,
|
oldDataZoomOption: newDataZoomOption,
|
||||||
};
|
});
|
||||||
renderTableInfo();
|
renderTableInfo();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -217,23 +241,14 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 3. 滑块拖动后缩放比例变小
|
// 3. 滑块拖动后缩放比例变小
|
||||||
// 判断拖动后选择的时间范围并提示
|
|
||||||
if (newEndSliderPos - newStartSliderPos < LEAST_SELECTED_TIME_RANGE) {
|
|
||||||
// TODO: 补充逻辑
|
|
||||||
updateChartData([oldStartTimestamp, oldEndTimestamp], [oldStartSliderPos, oldEndSliderPos]);
|
|
||||||
message.warning(`当前选择范围小于 ${LEAST_SELECTED_TIME_RANGE / 60 / 1000} 分钟,图表可能无数据`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOldLarger = oldScale - DATA_ZOOM_DEFAULT_SCALE > 0.01;
|
const isOldLarger = oldScale - DATA_ZOOM_DEFAULT_SCALE > 0.01;
|
||||||
const isNewLarger = newScale - DATA_ZOOM_DEFAULT_SCALE > 0.01;
|
const isNewLarger = newScale - DATA_ZOOM_DEFAULT_SCALE > 0.01;
|
||||||
if (isOldLarger && isNewLarger) {
|
if (isOldLarger && isNewLarger) {
|
||||||
// 如果拖拽前后比例均高于默认比例,则不对图表展示范围进行操作
|
// 如果拖拽前后比例均高于默认比例,则不对图表展示范围进行操作
|
||||||
chartInfo.current = {
|
updateChartInfo({
|
||||||
...chartInfo.current,
|
|
||||||
sliderPos: [newStartSliderPos, newEndSliderPos],
|
sliderPos: [newStartSliderPos, newEndSliderPos],
|
||||||
oldDataZoomOption: newDataZoomOption,
|
oldDataZoomOption: newDataZoomOption,
|
||||||
};
|
});
|
||||||
renderTableInfo();
|
renderTableInfo();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -259,79 +274,98 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
const updateChartData = (timeRange: [number, number], sliderPos: [number, number]) => {
|
const updateChartData = (timeRange: [number, number], sliderPos: [number, number]) => {
|
||||||
const {
|
const {
|
||||||
fullTimeRange: [fullStartTimestamp, fullEndTimestamp],
|
fullTimeRange: [fullStartTimestamp, fullEndTimestamp],
|
||||||
fullMetricData,
|
|
||||||
isLoadedFullData,
|
isLoadedFullData,
|
||||||
} = chartInfo.current;
|
} = chartInfo.current;
|
||||||
let leftBoundaryTimestamp = Math.floor(timeRange[0]);
|
const leftBoundaryTimestamp = Math.floor(timeRange[0]);
|
||||||
const isNeedCacheExtraData = leftBoundaryTimestamp < fullStartTimestamp + (fullEndTimestamp - fullStartTimestamp) * PRECACHE_THRESHOLD;
|
const isNeedCacheExtraData = leftBoundaryTimestamp < fullStartTimestamp + (fullEndTimestamp - fullStartTimestamp) * PRECACHE_THRESHOLD;
|
||||||
|
|
||||||
let isRendered = false;
|
let isRendered = false;
|
||||||
// 如果本地存储的数据足够展示或者已经获取到所有数据,则展示数据
|
// 如果本地存储的数据足够展示或者已经获取到所有数据,则展示数据
|
||||||
if (leftBoundaryTimestamp > fullStartTimestamp || isLoadedFullData) {
|
if (leftBoundaryTimestamp > fullStartTimestamp || isLoadedFullData) {
|
||||||
chartInfo.current = {
|
updateChartInfo({
|
||||||
...chartInfo.current,
|
|
||||||
curTimeRange: [leftBoundaryTimestamp > fullStartTimestamp ? leftBoundaryTimestamp : fullStartTimestamp, timeRange[1]],
|
curTimeRange: [leftBoundaryTimestamp > fullStartTimestamp ? leftBoundaryTimestamp : fullStartTimestamp, timeRange[1]],
|
||||||
sliderPos,
|
sliderPos,
|
||||||
};
|
});
|
||||||
renderNewMetricData();
|
renderNewMetricData();
|
||||||
isRendered = true;
|
isRendered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoadedFullData && isNeedCacheExtraData) {
|
if (!isLoadedFullData && isNeedCacheExtraData) {
|
||||||
// 向服务器请求新的数据缓存
|
getAdditionChartData(!isRendered, leftBoundaryTimestamp, timeRange[1], sliderPos);
|
||||||
let reqEndTime = fullStartTimestamp;
|
}
|
||||||
const requestArr: any[] = [];
|
};
|
||||||
const requestTimeRanges: [number, number][] = [];
|
|
||||||
for (let i = 0; i < DEFAULT_REQUEST_COUNT; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const nextReqEndTime = reqEndTime - DEFAULT_REQUEST_TIME_RANGE;
|
|
||||||
requestArr.unshift(getMetricChartData([nextReqEndTime, reqEndTime]));
|
|
||||||
requestTimeRanges.unshift([nextReqEndTime, reqEndTime]);
|
|
||||||
reqEndTime = nextReqEndTime;
|
|
||||||
|
|
||||||
// 当最后一次请求发送后,处理返回
|
// 缓存增量的图表数据
|
||||||
if (i === DEFAULT_REQUEST_COUNT - 1) {
|
const getAdditionChartData = (
|
||||||
Promise.all(requestArr).then((resList) => {
|
needRender: boolean,
|
||||||
let isSettle = -1;
|
leftBoundaryTimestamp: number,
|
||||||
// 填充增量的图表数据
|
rightBoundaryTimestamp: number,
|
||||||
resList.forEach((res: MetricDefaultChartDataType[], i) => {
|
sliderPos?: [number, number]
|
||||||
// 图表没有返回数据的情况
|
) => {
|
||||||
if (!res?.length) {
|
const {
|
||||||
if (isSettle === -1) {
|
fullTimeRange: [fullStartTimestamp, fullEndTimestamp],
|
||||||
chartInfo.current = {
|
fullMetricData,
|
||||||
...chartInfo.current,
|
isLoadingAdditionData,
|
||||||
// 标记数据已经全部加载完毕
|
} = chartInfo.current;
|
||||||
isLoadedFullData: true,
|
|
||||||
};
|
|
||||||
isSettle = i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolveAdditionChartData(res, requestTimeRanges[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 更新左侧边界为当前已获取到数据的最小边界
|
|
||||||
const curLocalStartTimestamp = Number(fullMetricData.metricLines.map((line) => line.data[0][0]).sort()[0]);
|
|
||||||
if (leftBoundaryTimestamp < curLocalStartTimestamp) {
|
|
||||||
leftBoundaryTimestamp = curLocalStartTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
chartInfo.current = {
|
// 当前有缓存数据的任务时,直接退出
|
||||||
...chartInfo.current,
|
if (isLoadingAdditionData) {
|
||||||
fullTimeRange: [reqEndTime - DEFAULT_REQUEST_TIME_RANGE, fullEndTimestamp],
|
return false;
|
||||||
sliderPos,
|
}
|
||||||
};
|
updateChartInfo({
|
||||||
if (!isRendered) {
|
isLoadingAdditionData: true,
|
||||||
chartInfo.current = {
|
});
|
||||||
...chartInfo.current,
|
|
||||||
curTimeRange: [leftBoundaryTimestamp, timeRange[1]],
|
let reqEndTime = fullStartTimestamp;
|
||||||
};
|
const requestArr: any[] = [];
|
||||||
renderNewMetricData();
|
const requestTimeRanges: [number, number][] = [];
|
||||||
|
for (let i = 0; i < DEFAULT_REQUEST_COUNT; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const nextReqEndTime = reqEndTime - DEFAULT_REQUEST_TIME_RANGE;
|
||||||
|
requestArr.push(getMetricChartData([nextReqEndTime, reqEndTime]));
|
||||||
|
requestTimeRanges.push([nextReqEndTime, reqEndTime]);
|
||||||
|
reqEndTime = nextReqEndTime;
|
||||||
|
|
||||||
|
// 当最后一次请求发送后,处理返回
|
||||||
|
if (i === DEFAULT_REQUEST_COUNT - 1) {
|
||||||
|
Promise.all(requestArr).then((resList) => {
|
||||||
|
// 填充增量的图表数据
|
||||||
|
resList.forEach((res: MetricDefaultChartDataType[], i) => {
|
||||||
|
// 最后一个请求返回数据为空时,认为已获取到全部图表数据
|
||||||
|
if (!res?.length) {
|
||||||
|
// 标记数据已经全部加载完毕
|
||||||
|
i === resList.length - 1 &&
|
||||||
|
updateChartInfo({
|
||||||
|
isLoadedFullData: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO: res 可能为 [],需要处理兼容
|
||||||
|
resolveAdditionChartData(res, requestTimeRanges[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}, i * 10);
|
// 更新左侧边界为当前已获取到数据的最小边界
|
||||||
}
|
const curLocalStartTimestamp = Number(fullMetricData.metricLines.map((line) => line?.data?.[0]?.[0]).sort()[0]);
|
||||||
|
if (leftBoundaryTimestamp < curLocalStartTimestamp) {
|
||||||
|
leftBoundaryTimestamp = curLocalStartTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartInfo({
|
||||||
|
fullTimeRange: [reqEndTime - DEFAULT_REQUEST_TIME_RANGE, fullEndTimestamp],
|
||||||
|
...(sliderPos ? { sliderPos } : {}),
|
||||||
|
isLoadingAdditionData: false,
|
||||||
|
});
|
||||||
|
if (needRender) {
|
||||||
|
updateChartInfo({
|
||||||
|
curTimeRange: [leftBoundaryTimestamp, rightBoundaryTimestamp],
|
||||||
|
});
|
||||||
|
renderNewMetricData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, i * 10);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理增量图表数据
|
// 处理增量图表数据
|
||||||
@@ -362,7 +396,7 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据需要展示的时间范围过滤出对应的数据展示
|
// 根据需要展示的时间范围过滤出对应的数据
|
||||||
const renderNewMetricData = () => {
|
const renderNewMetricData = () => {
|
||||||
const { fullMetricData, curTimeRange } = chartInfo.current;
|
const { fullMetricData, curTimeRange } = chartInfo.current;
|
||||||
const newMetricData = { ...fullMetricData };
|
const newMetricData = { ...fullMetricData };
|
||||||
@@ -378,12 +412,25 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
});
|
});
|
||||||
newMetricData.metricLines[i] = line;
|
newMetricData.metricLines[i] = line;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 只过滤出当前时间段有数据点的线条,确保 Table 统一展示
|
// 只过滤出当前时间段有数据点的线条,确保 Table 统一展示
|
||||||
newMetricData.metricLines = newMetricData.metricLines.filter((line) => line.data.length);
|
newMetricData.metricLines = newMetricData.metricLines.filter((line) => line.data.length);
|
||||||
setCurMetricData(newMetricData);
|
setCurMetricData(newMetricData);
|
||||||
|
|
||||||
|
setLinesStatus((curStatus) => {
|
||||||
|
// 过滤维持线条选中状态
|
||||||
|
const newLinesStatus = { ...curStatus };
|
||||||
|
const newLineNames = newMetricData.metricLines.map((line) => line.name);
|
||||||
|
newLineNames.forEach((name) => {
|
||||||
|
if (newLinesStatus[name] === undefined) {
|
||||||
|
newLinesStatus[name] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newLinesStatus;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算当前选中范围
|
// 计算展示当前拖拽轴选中的时间范围
|
||||||
const calculateSliderRange = () => {
|
const calculateSliderRange = () => {
|
||||||
const { sliderPos } = chartInfo.current;
|
const { sliderPos } = chartInfo.current;
|
||||||
let minutes = Number(((sliderPos[1] - sliderPos[0]) / 60 / 1000).toFixed(2));
|
let minutes = Number(((sliderPos[1] - sliderPos[0]) / 60 / 1000).toFixed(2));
|
||||||
@@ -398,13 +445,11 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
hours = Number((hours % 24).toFixed(2));
|
hours = Number((hours % 24).toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
chartInfo.current = {
|
const sliderRange = ` 当前选中范围: ${days > 0 ? `${days} 天 ` : ''}${hours > 0 ? `${hours} 小时 ` : ''}${minutes} 分钟`;
|
||||||
...chartInfo.current,
|
setSliderRange(sliderRange);
|
||||||
sliderRange: ` 当前选中范围: ${days > 0 ? `${days} 天 ` : ''}${hours > 0 ? `${hours} 小时 ` : ''}${minutes} 分钟`,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 遍历图表,获取需要的指标数据,展示到 Table
|
// 遍历图表,计算得到指标聚合数据展示到表格
|
||||||
const renderTableInfo = () => {
|
const renderTableInfo = () => {
|
||||||
const tableData: MetricTableInfo[] = [];
|
const tableData: MetricTableInfo[] = [];
|
||||||
const { sliderPos, chartInstance } = chartInfo.current;
|
const { sliderPos, chartInstance } = chartInfo.current;
|
||||||
@@ -447,140 +492,131 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
|
|
||||||
calculateSliderRange();
|
calculateSliderRange();
|
||||||
setTableInfo(tableData);
|
setTableInfo(tableData);
|
||||||
setSelectedLines(tableData.map((line) => line.name));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tableLineChange = (keys: string[]) => {
|
const tableLineChange = (keys: string[]) => {
|
||||||
const updatedLines: { [name: string]: boolean } = {};
|
const newLinesStatus = { ...linesStatus };
|
||||||
selectedLines.forEach((name) => !keys.includes(name) && (updatedLines[name] = false));
|
|
||||||
keys.forEach((name) => !selectedLines.includes(name) && (updatedLines[name] = true));
|
|
||||||
|
|
||||||
// 更新
|
Object.entries(newLinesStatus).forEach(([name, status]) => {
|
||||||
Object.keys(updatedLines).forEach((name) => {
|
if (keys.includes(name)) {
|
||||||
chartInfo.current.chartInstance.dispatchAction({
|
!status && (newLinesStatus[name] = true);
|
||||||
type: 'legendToggleSelect',
|
} else {
|
||||||
// 图例名称
|
status && (newLinesStatus[name] = false);
|
||||||
name: name,
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setSelectedLines(keys);
|
setLinesStatus(newLinesStatus);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 图表数据更新渲染后,更新图表拖拽轴信息并重新计算列表值
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (curMetricData) {
|
if (curMetricData) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 新的图表数据渲染后,更新图表拖拽轴信息
|
|
||||||
chartInfo.current.oldDataZoomOption = (chartInfo.current.chartInstance.getOption() as any).dataZoom[0];
|
chartInfo.current.oldDataZoomOption = (chartInfo.current.chartInstance.getOption() as any).dataZoom[0];
|
||||||
});
|
});
|
||||||
renderTableInfo();
|
renderTableInfo();
|
||||||
}
|
}
|
||||||
}, [curMetricData]);
|
}, [curMetricData]);
|
||||||
|
|
||||||
|
// 更新图例选中状态
|
||||||
|
useEffect(() => {
|
||||||
|
Object.entries(linesStatus).map(([name, status]) => {
|
||||||
|
const type = status ? 'legendSelect' : 'legendUnSelect';
|
||||||
|
chartInfo.current.chartInstance.dispatchAction({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [linesStatus]);
|
||||||
|
|
||||||
// 进入详情时,首次获取数据
|
// 进入详情时,首次获取数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metricType && metricName) {
|
if (metricType && metricName) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { curTimeRange } = chartInfo.current;
|
const { curTimeRange } = chartInfo.current;
|
||||||
getMetricChartData(curTimeRange).then((res: any[] | null) => {
|
getMetricChartData(curTimeRange).then(
|
||||||
// 如果图表返回数据
|
(res: any[] | null) => {
|
||||||
if (res?.length) {
|
// 如果图表返回数据
|
||||||
// 格式化图表需要的数据
|
if (res?.length) {
|
||||||
const formattedMetricData = (
|
// 格式化图表需要的数据
|
||||||
formatChartData(
|
const formattedMetricData = (
|
||||||
res,
|
formatChartData(
|
||||||
global.getMetricDefine || {},
|
res,
|
||||||
metricType,
|
global.getMetricDefine || {},
|
||||||
curTimeRange,
|
metricType,
|
||||||
DEFAULT_POINT_INTERVAL,
|
curTimeRange,
|
||||||
false
|
DEFAULT_POINT_INTERVAL,
|
||||||
) as MetricChartDataType[]
|
false
|
||||||
)[0];
|
) as MetricChartDataType[]
|
||||||
// 填充图表数据
|
)[0];
|
||||||
let initFullTimeRange = curTimeRange;
|
// 填充图表数据
|
||||||
const pointsOfFirstLine = formattedMetricData.metricLines.find((line) => line.data.length).data;
|
let initFullTimeRange = curTimeRange;
|
||||||
if (pointsOfFirstLine) {
|
const pointsOfFirstLine = formattedMetricData.metricLines.find((line) => line.data.length).data;
|
||||||
initFullTimeRange = [pointsOfFirstLine[0][0] as number, pointsOfFirstLine[pointsOfFirstLine.length - 1][0] as number] as const;
|
if (pointsOfFirstLine) {
|
||||||
}
|
initFullTimeRange = [
|
||||||
|
pointsOfFirstLine[0][0] as number,
|
||||||
// 获取单位保存起来
|
pointsOfFirstLine[pointsOfFirstLine.length - 1][0] as number,
|
||||||
let transformUnit = undefined;
|
] as const;
|
||||||
Object.entries(UNIT_MAP).forEach((unit) => {
|
|
||||||
if (formattedMetricData.metricUnit.includes(unit[0])) {
|
|
||||||
transformUnit = unit;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
chartInfo.current = {
|
// 获取单位保存起来
|
||||||
...chartInfo.current,
|
let transformUnit = undefined;
|
||||||
fullMetricData: formattedMetricData,
|
Object.entries(UNIT_MAP).forEach((unit) => {
|
||||||
fullTimeRange: [...initFullTimeRange],
|
if (formattedMetricData.metricUnit.includes(unit[0])) {
|
||||||
curTimeRange: [...initFullTimeRange],
|
transformUnit = unit;
|
||||||
sliderPos: [
|
}
|
||||||
initFullTimeRange[1] - (initFullTimeRange[1] - initFullTimeRange[0]) * DATA_ZOOM_DEFAULT_SCALE,
|
});
|
||||||
initFullTimeRange[1],
|
|
||||||
],
|
updateChartInfo({
|
||||||
transformUnit,
|
fullMetricData: formattedMetricData,
|
||||||
};
|
fullTimeRange: [...initFullTimeRange],
|
||||||
setCurMetricData(formattedMetricData);
|
curTimeRange: [...initFullTimeRange],
|
||||||
setLoading(false);
|
sliderPos: [
|
||||||
}
|
initFullTimeRange[1] - (initFullTimeRange[1] - initFullTimeRange[0]) * DATA_ZOOM_DEFAULT_SCALE,
|
||||||
});
|
initFullTimeRange[1],
|
||||||
|
],
|
||||||
|
transformUnit,
|
||||||
|
});
|
||||||
|
setCurMetricData(formattedMetricData);
|
||||||
|
const newLinesStatus: { [lineName: string]: boolean } = {};
|
||||||
|
formattedMetricData.metricLines.forEach((line) => {
|
||||||
|
newLinesStatus[line.name] = true;
|
||||||
|
});
|
||||||
|
setLinesStatus(newLinesStatus);
|
||||||
|
setLoading(false);
|
||||||
|
getAdditionChartData(false, initFullTimeRange[0], initFullTimeRange[1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => setLoading(false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const debounced = debounce(onDataZoomDrag, 300);
|
debouncedZoomDrag.current = debounce(onDataZoomDrag, 300);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<div className="chart-detail-modal-container">
|
<div className="chart-detail-modal-container">
|
||||||
{curMetricData && (
|
{curMetricData ? (
|
||||||
<>
|
<>
|
||||||
<div className="detail-title">
|
|
||||||
<div className="left">
|
|
||||||
<div className="title">
|
|
||||||
<Tooltip
|
|
||||||
placement="bottomLeft"
|
|
||||||
title={() => {
|
|
||||||
let content = '';
|
|
||||||
const metricDefine = global.getMetricDefine(metricType, curMetricData.metricName);
|
|
||||||
if (metricDefine) {
|
|
||||||
content = metricDefine.desc;
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ cursor: 'pointer' }}>
|
|
||||||
<span>{curMetricData.metricName}</span> <span className="unit">({curMetricData.metricUnit}) </span>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className="info">{chartInfo.current.sliderRange}</div>
|
|
||||||
</div>
|
|
||||||
<div className="right">
|
|
||||||
<Button type="text" size="small" onClick={onClose}>
|
|
||||||
<CloseOutlined />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SingleChart
|
<SingleChart
|
||||||
chartTypeProp="line"
|
chartTypeProp="line"
|
||||||
wrapStyle={{
|
wrapStyle={{
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 462,
|
height: 462,
|
||||||
}}
|
}}
|
||||||
|
// events 事件只注册一次,所以这里使用 ref 来执行防抖函数
|
||||||
onEvents={{
|
onEvents={{
|
||||||
dataZoom: (record: any) => {
|
dataZoom: (record: any) => debouncedZoomDrag?.current(record),
|
||||||
debounced(record);
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
|
showHeader={false}
|
||||||
propChartData={curMetricData.metricLines}
|
propChartData={curMetricData.metricLines}
|
||||||
optionMergeProps={{ notMerge: true }}
|
optionMergeProps={{ notMerge: true }}
|
||||||
getChartInstance={(chartInstance) => {
|
getChartInstance={(chartInstance) => {
|
||||||
chartInfo.current = {
|
setDisposeChartInstance(() => () => chartInstance.dispose());
|
||||||
...chartInfo.current,
|
updateChartInfo({
|
||||||
chartInstance,
|
chartInstance,
|
||||||
};
|
});
|
||||||
}}
|
}}
|
||||||
{...getDetailChartConfig(`${curMetricData.metricName}{unit|(${curMetricData.metricUnit})}`, chartInfo.current.sliderPos)}
|
{...getDetailChartConfig(`${curMetricData.metricName}{unit|(${curMetricData.metricUnit})}`, chartInfo.current.sliderPos)}
|
||||||
/>
|
/>
|
||||||
@@ -588,16 +624,10 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
className="detail-table"
|
className="detail-table"
|
||||||
rowKey="name"
|
rowKey="name"
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
// hideSelectAll: true,
|
|
||||||
preserveSelectedRowKeys: true,
|
preserveSelectedRowKeys: true,
|
||||||
selectedRowKeys: selectedLines,
|
selectedRowKeys: Object.entries(linesStatus)
|
||||||
// getCheckboxProps: (record) => {
|
.filter(([, status]) => status)
|
||||||
// return selectedLines.length <= 1 && selectedLines.includes(record.name)
|
.map(([name]) => name),
|
||||||
// ? {
|
|
||||||
// disabled: true,
|
|
||||||
// }
|
|
||||||
// : {};
|
|
||||||
// },
|
|
||||||
selections: [Table.SELECTION_INVERT, Table.SELECTION_NONE],
|
selections: [Table.SELECTION_INVERT, Table.SELECTION_NONE],
|
||||||
onChange: (keys: string[]) => tableLineChange(keys),
|
onChange: (keys: string[]) => tableLineChange(keys),
|
||||||
}}
|
}}
|
||||||
@@ -610,6 +640,8 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
!loading && <RenderEmpty message="详情加载失败,请重试" height={400} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
@@ -618,22 +650,46 @@ const ChartDetail = (props: ChartDetailProps) => {
|
|||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const ChartDrawer = forwardRef((_, ref) => {
|
const ChartDrawer = forwardRef((_, ref) => {
|
||||||
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [dashboardType, setDashboardType] = useState<MetricType>();
|
|
||||||
const [metricName, setMetricName] = useState<string>();
|
|
||||||
const [queryLines, setQueryLines] = useState<string[]>([]);
|
const [queryLines, setQueryLines] = useState<string[]>([]);
|
||||||
|
const [sliderRange, setSliderRange] = useState<string>('');
|
||||||
|
const [disposeChartInstance, setDisposeChartInstance] = useState<() => void>(() => 0);
|
||||||
|
const [metricInfo, setMetricInfo] = useState<{
|
||||||
|
type: MetricType | undefined;
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
desc: string;
|
||||||
|
}>({
|
||||||
|
type: undefined,
|
||||||
|
name: '',
|
||||||
|
unit: '',
|
||||||
|
desc: '',
|
||||||
|
});
|
||||||
|
|
||||||
const onOpen = (dashboardType: MetricType, metricName: string, queryLines: string[]) => {
|
const onOpen = (dashboardType: MetricType, metricName: string, queryLines: string[]) => {
|
||||||
setDashboardType(dashboardType);
|
const metricDefine = global.getMetricDefine(dashboardType, metricName);
|
||||||
setMetricName(metricName);
|
setMetricInfo({
|
||||||
|
type: dashboardType,
|
||||||
|
name: metricName,
|
||||||
|
unit: metricDefine?.unit || '',
|
||||||
|
desc: metricDefine?.desc || '',
|
||||||
|
});
|
||||||
setQueryLines(queryLines);
|
setQueryLines(queryLines);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setDashboardType(undefined);
|
setSliderRange('');
|
||||||
setMetricName(undefined);
|
disposeChartInstance();
|
||||||
|
setDisposeChartInstance(() => () => 0);
|
||||||
|
setMetricInfo({
|
||||||
|
type: undefined,
|
||||||
|
name: '',
|
||||||
|
unit: '',
|
||||||
|
desc: '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -641,9 +697,36 @@ const ChartDrawer = forwardRef((_, ref) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer width={1080} visible={visible} footer={null} closable={false} maskClosable={false} destroyOnClose={true} onClose={onClose}>
|
<Drawer
|
||||||
{dashboardType && metricName && (
|
className="overview-chart-detail-drawer"
|
||||||
<ChartDetail metricType={dashboardType} metricName={metricName} queryLines={queryLines} onClose={onClose} />
|
width={1080}
|
||||||
|
visible={visible}
|
||||||
|
title={
|
||||||
|
<div className="detail-header">
|
||||||
|
<div className="title">
|
||||||
|
<Tooltip placement="bottomLeft" title={metricInfo.desc}>
|
||||||
|
<span style={{ cursor: 'pointer' }}>
|
||||||
|
<span>{metricInfo.name}</span> <span className="unit">({metricInfo.unit}) </span>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="slider-info">{sliderRange}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
footer={null}
|
||||||
|
closable={true}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose={true}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
{metricInfo.type && metricInfo.name && (
|
||||||
|
<ChartDetail
|
||||||
|
metricType={metricInfo.type}
|
||||||
|
metricName={metricInfo.name}
|
||||||
|
queryLines={queryLines}
|
||||||
|
setSliderRange={setSliderRange}
|
||||||
|
setDisposeChartInstance={setDisposeChartInstance}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,30 +46,42 @@ export const supplementaryPoints = (
|
|||||||
extraCallback?: (point: [number, 0]) => any[]
|
extraCallback?: (point: [number, 0]) => any[]
|
||||||
) => {
|
) => {
|
||||||
lines.forEach(({ data }) => {
|
lines.forEach(({ data }) => {
|
||||||
|
// 获取未补点前线条的点的个数
|
||||||
let len = data.length;
|
let len = data.length;
|
||||||
for (let i = 0; i < len; i++) {
|
// 记录当前处理到的点的下标值
|
||||||
const timestamp = data[i][0] as number;
|
let i = 0;
|
||||||
// 数组第一个点和最后一个点单独处理
|
|
||||||
|
for (; i < len; i++) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
let firstPointTimestamp = data[0][0] as number;
|
let firstPointTimestamp = data[0][0] as number;
|
||||||
while (firstPointTimestamp - interval > timeRange[0]) {
|
while (firstPointTimestamp - interval > timeRange[0]) {
|
||||||
const prePointTimestamp = firstPointTimestamp - interval;
|
const prevPointTimestamp = firstPointTimestamp - interval;
|
||||||
data.unshift(extraCallback ? extraCallback([prePointTimestamp, 0]) : [prePointTimestamp, 0]);
|
data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
|
||||||
|
firstPointTimestamp = prevPointTimestamp;
|
||||||
len++;
|
len++;
|
||||||
i++;
|
i++;
|
||||||
firstPointTimestamp = prePointTimestamp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === len - 1) {
|
if (i === len - 1) {
|
||||||
let lastPointTimestamp = data[len - 1][0] as number;
|
let lastPointTimestamp = data[i][0] as number;
|
||||||
while (lastPointTimestamp + interval < timeRange[1]) {
|
while (lastPointTimestamp + interval < timeRange[1]) {
|
||||||
const next = lastPointTimestamp + interval;
|
const nextPointTimestamp = lastPointTimestamp + interval;
|
||||||
data.push(extraCallback ? extraCallback([next, 0]) : [next, 0]);
|
data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
|
||||||
lastPointTimestamp = next;
|
lastPointTimestamp = nextPointTimestamp;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let timestamp = data[i][0] as number;
|
||||||
|
while (timestamp + interval < data[i + 1][0]) {
|
||||||
|
const nextPointTimestamp = timestamp + interval;
|
||||||
|
data.splice(i + 1, 0, extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
|
||||||
|
timestamp = nextPointTimestamp;
|
||||||
|
len++;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
} else if (timestamp + interval < data[i + 1][0]) {
|
|
||||||
data.splice(i + 1, 0, extraCallback ? extraCallback([timestamp + interval, 0]) : [timestamp + interval, 0]);
|
|
||||||
len++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -135,18 +147,37 @@ export const formatChartData = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => {
|
const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => {
|
||||||
|
const len = CHART_COLOR_LIST.length;
|
||||||
// series 配置
|
// series 配置
|
||||||
return lines.map((line) => {
|
return lines.map((line, i) => {
|
||||||
return {
|
return {
|
||||||
...line,
|
...line,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
},
|
},
|
||||||
|
connectNulls: false,
|
||||||
symbol: 'emptyCircle',
|
symbol: 'emptyCircle',
|
||||||
symbolSize: 4,
|
symbolSize: 4,
|
||||||
smooth: 0.25,
|
smooth: 0.25,
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
opacity: 0.02,
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: CHART_COLOR_LIST[i % len] + '10',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(255,255,255,0)', // 100% 处的颜色
|
||||||
|
},
|
||||||
|
],
|
||||||
|
global: false, // 缺省为 false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -189,6 +220,7 @@ export const getDetailChartConfig = (title: string, sliderPos: readonly [number,
|
|||||||
startValue: sliderPos[0],
|
startValue: sliderPos[0],
|
||||||
endValue: sliderPos[1],
|
endValue: sliderPos[1],
|
||||||
zoomOnMouseWheel: false,
|
zoomOnMouseWheel: false,
|
||||||
|
minValueSpan: 10 * 60 * 1000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
|
|||||||
@@ -63,56 +63,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overview-chart-detail-drawer {
|
||||||
.chart-detail-modal-container {
|
.dcloud-spin-nested-loading > div > .dcloud-spin.dcloud-spin-spinning {
|
||||||
position: relative;
|
height: 300px;
|
||||||
.expand-icon-box {
|
}
|
||||||
position: absolute;
|
&.dcloud-drawer .dcloud-drawer-body {
|
||||||
z-index: 1000;
|
padding: 0 20px;
|
||||||
top: 14px;
|
}
|
||||||
right: 44px;
|
.detail-header {
|
||||||
width: 24px;
|
display: flex;
|
||||||
height: 24px;
|
align-items: flex-end;
|
||||||
cursor: pointer;
|
font-weight: normal;
|
||||||
font-size: 16px;
|
.title {
|
||||||
text-align: center;
|
font-family: @font-family-bold;
|
||||||
border-radius: 50%;
|
font-size: 18px;
|
||||||
transition: background-color 0.3s ease;
|
color: #495057;
|
||||||
.expand-icon {
|
letter-spacing: 0;
|
||||||
color: #adb5bc;
|
.unit {
|
||||||
line-height: 24px;
|
font-family: @font-family-bold;
|
||||||
}
|
font-size: 14px;
|
||||||
&:hover {
|
letter-spacing: 0.5px;
|
||||||
background: rgba(33, 37, 41, 0.04);
|
|
||||||
.expand-icon {
|
|
||||||
color: #74788d;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.slider-info {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: @font-family;
|
||||||
|
color: #303a51;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.detail-title {
|
.chart-detail-modal-container {
|
||||||
display: flex;
|
position: relative;
|
||||||
justify-content: space-between;
|
overflow: hidden;
|
||||||
align-items: center;
|
.expand-icon-box {
|
||||||
.left {
|
position: absolute;
|
||||||
display: flex;
|
z-index: 1000;
|
||||||
align-items: flex-end;
|
top: 14px;
|
||||||
.title {
|
right: 44px;
|
||||||
font-family: @font-family-bold;
|
width: 24px;
|
||||||
font-size: 18px;
|
height: 24px;
|
||||||
color: #495057;
|
cursor: pointer;
|
||||||
letter-spacing: 0;
|
font-size: 16px;
|
||||||
.unit {
|
text-align: center;
|
||||||
font-family: @font-family-bold;
|
border-radius: 50%;
|
||||||
font-size: 14px;
|
transition: background-color 0.3s ease;
|
||||||
letter-spacing: 0.5px;
|
.expand-icon {
|
||||||
|
color: #adb5bc;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: rgba(33, 37, 41, 0.04);
|
||||||
|
.expand-icon {
|
||||||
|
color: #74788d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.info {
|
}
|
||||||
margin-left: 10px;
|
.detail-table {
|
||||||
}
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.detail-table {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,8 +216,8 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
|||||||
onChange={ksHeaderChange}
|
onChange={ksHeaderChange}
|
||||||
nodeScopeModule={{
|
nodeScopeModule={{
|
||||||
customScopeList: scopeList,
|
customScopeList: scopeList,
|
||||||
scopeName: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`,
|
scopeName: dashboardType === MetricType.Broker ? 'Broker' : 'Topic',
|
||||||
showSearch: dashboardType === MetricType.Topic,
|
scopeLabel: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`,
|
||||||
}}
|
}}
|
||||||
indicatorSelectModule={{
|
indicatorSelectModule={{
|
||||||
hide: false,
|
hide: false,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const RenderEmpty = (props: { height?: string | number; message: string }) => {
|
||||||
|
const { height = 200, message } = props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="empty-panel" style={{ height }}>
|
||||||
|
<div className="img" />
|
||||||
|
<div className="text">{message}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderEmpty;
|
||||||
@@ -26,8 +26,8 @@ const OptionsDefault = [
|
|||||||
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||||
const {
|
const {
|
||||||
customScopeList: customList,
|
customScopeList: customList,
|
||||||
scopeName = '自定义节点范围',
|
scopeName = '',
|
||||||
showSearch = false,
|
scopeLabel = '自定义范围',
|
||||||
searchPlaceholder = '输入内容进行搜索',
|
searchPlaceholder = '输入内容进行搜索',
|
||||||
} = nodeScopeModule;
|
} = nodeScopeModule;
|
||||||
const [topNum, setTopNum] = useState<number>(5);
|
const [topNum, setTopNum] = useState<number>(5);
|
||||||
@@ -70,7 +70,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
change(checkedListTemp, false);
|
change(checkedListTemp, false);
|
||||||
setIsTop(false);
|
setIsTop(false);
|
||||||
setTopNum(null);
|
setTopNum(null);
|
||||||
setInputValue(`已选${checkedListTemp?.length}项`);
|
setInputValue(`${checkedListTemp?.length}项`);
|
||||||
setPopVisible(false);
|
setPopVisible(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -109,7 +109,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
{/* <span>时间:</span> */}
|
{/* <span>时间:</span> */}
|
||||||
<div className="flx_con">
|
<div className="flx_con">
|
||||||
<div className="flx_l">
|
<div className="flx_l">
|
||||||
<h6 className="time_title">选择top范围</h6>
|
<h6 className="time_title">选择 top 范围</h6>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
optionType="button"
|
optionType="button"
|
||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
@@ -128,7 +128,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
<div className="flx_r">
|
<div className="flx_r">
|
||||||
<h6 className="time_title">{scopeName}</h6>
|
<h6 className="time_title">{scopeLabel}</h6>
|
||||||
<div className="custom-scope">
|
<div className="custom-scope">
|
||||||
<div className="check-row">
|
<div className="check-row">
|
||||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||||
@@ -136,9 +136,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Input
|
<Input
|
||||||
className="search-input"
|
className="search-input"
|
||||||
suffix={
|
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||||
<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />
|
|
||||||
}
|
|
||||||
size="small"
|
size="small"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||||
@@ -148,7 +146,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||||
<Row gutter={[10, 12]}>
|
<Row gutter={[10, 12]}>
|
||||||
{customList
|
{customList
|
||||||
.filter((item) => !showSearch || item.label.includes(scopeSearchValue))
|
.filter((item) => item.label.includes(scopeSearchValue))
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<Col span={12} key={item.value}>
|
<Col span={12} key={item.value}>
|
||||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||||
@@ -180,6 +178,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="d-node-scope">
|
<div id="d-node-scope">
|
||||||
|
<div className="scope-title">{scopeName}筛选:</div>
|
||||||
<Popover
|
<Popover
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
visible={popVisible}
|
visible={popVisible}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Tooltip, Select, IconFont, Utils, Divider } from 'knowdesign';
|
import { Tooltip, Select, IconFont, Utils, Divider, Button } from 'knowdesign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { DRangeTime } from 'knowdesign';
|
import { DRangeTime } from 'knowdesign';
|
||||||
import IndicatorDrawer from './IndicatorDrawer';
|
import IndicatorDrawer from './IndicatorDrawer';
|
||||||
@@ -48,7 +48,7 @@ export interface IcustomScope {
|
|||||||
export interface InodeScopeModule {
|
export interface InodeScopeModule {
|
||||||
customScopeList: IcustomScope[];
|
customScopeList: IcustomScope[];
|
||||||
scopeName?: string;
|
scopeName?: string;
|
||||||
showSearch?: boolean;
|
scopeLabel?: string;
|
||||||
searchPlaceholder?: string;
|
searchPlaceholder?: string;
|
||||||
change?: () => void;
|
change?: () => void;
|
||||||
}
|
}
|
||||||
@@ -138,9 +138,13 @@ const SingleChartHeader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reloadRangeTime = () => {
|
const reloadRangeTime = () => {
|
||||||
const timeLen = rangeTime[1] - rangeTime[0] || 0;
|
if (isRelativeRangeTime) {
|
||||||
const curTimeStamp = moment().valueOf();
|
const timeLen = rangeTime[1] - rangeTime[0] || 0;
|
||||||
setRangeTime([curTimeStamp - timeLen, curTimeStamp]);
|
const curTimeStamp = moment().valueOf();
|
||||||
|
setRangeTime([curTimeStamp - timeLen, curTimeStamp]);
|
||||||
|
} else {
|
||||||
|
setRangeTime([...rangeTime]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openIndicatorDrawer = () => {
|
const openIndicatorDrawer = () => {
|
||||||
@@ -174,12 +178,10 @@ const SingleChartHeader = ({
|
|||||||
{!hideGridSelect && (
|
{!hideGridSelect && (
|
||||||
<Select className="grid-select" style={{ width: 70 }} value={gridNum} options={GRID_SIZE_OPTIONS} onChange={sizeChange} />
|
<Select className="grid-select" style={{ width: 70 }} value={gridNum} options={GRID_SIZE_OPTIONS} onChange={sizeChange} />
|
||||||
)}
|
)}
|
||||||
<Divider type="vertical" style={{ height: 20, top: 0 }} />
|
{(!hideNodeScope || !hideGridSelect) && <Divider type="vertical" style={{ height: 20, top: 0 }} />}
|
||||||
<Tooltip title="点击指标筛选,可选择指标" placement="bottomRight">
|
<Button type="primary" onClick={openIndicatorDrawer}>
|
||||||
<div className="icon-box" onClick={openIndicatorDrawer}>
|
指标筛选
|
||||||
<IconFont className="icon" type="icon-shezhi1" />
|
</Button>
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,8 +3,13 @@
|
|||||||
@import '~knowdesign/es/basic/style/mixins/index';
|
@import '~knowdesign/es/basic/style/mixins/index';
|
||||||
|
|
||||||
#d-node-scope {
|
#d-node-scope {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
.scope-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #74788d;
|
||||||
|
}
|
||||||
.input-span {
|
.input-span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -29,10 +34,10 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
&.relativeTime {
|
&.relativeTime {
|
||||||
width: 160px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
&.absoluteTime {
|
&.absoluteTime {
|
||||||
width: 300px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ const { TextArea } = Input;
|
|||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const jobNameMap: any = {
|
const jobNameMap: any = {
|
||||||
expandAndReduce: '批量扩缩副本',
|
expandAndReduce: '扩缩副本',
|
||||||
transfer: '批量迁移副本',
|
transfer: '迁移副本',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DefaultConfig {
|
interface DefaultConfig {
|
||||||
@@ -325,8 +325,7 @@ export default (props: DefaultConfig) => {
|
|||||||
!jobId &&
|
!jobId &&
|
||||||
Utils.request(Api.getTopicMetaData(+routeParams.clusterId))
|
Utils.request(Api.getTopicMetaData(+routeParams.clusterId))
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
const filterRes = res.filter((item: any) => item.type !== 1);
|
const topics = (res || []).map((item: any) => {
|
||||||
const topics = (filterRes || []).map((item: any) => {
|
|
||||||
return {
|
return {
|
||||||
label: item.topicName,
|
label: item.topicName,
|
||||||
value: item.topicName,
|
value: item.topicName,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
Transfer,
|
Transfer,
|
||||||
IconFont,
|
IconFont,
|
||||||
|
Tooltip,
|
||||||
} from 'knowdesign';
|
} from 'knowdesign';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import Api, { MetricType } from '@src/api/index';
|
import Api, { MetricType } from '@src/api/index';
|
||||||
@@ -31,8 +32,8 @@ const { TextArea } = Input;
|
|||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const jobNameMap: any = {
|
const jobNameMap: any = {
|
||||||
expandAndReduce: '批量扩缩副本',
|
expandAndReduce: '扩缩副本',
|
||||||
transfer: '批量迁移副本',
|
transfer: '迁移副本',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DefaultConfig {
|
interface DefaultConfig {
|
||||||
@@ -56,6 +57,7 @@ export default (props: DefaultConfig) => {
|
|||||||
const [topicNewReplicas, setTopicNewReplicas] = useState([]);
|
const [topicNewReplicas, setTopicNewReplicas] = useState([]);
|
||||||
const [needMovePartitions, setNeedMovePartitions] = useState([]);
|
const [needMovePartitions, setNeedMovePartitions] = useState([]);
|
||||||
const [moveDataTimeRanges, setMoveDataTimeRanges] = useState([]);
|
const [moveDataTimeRanges, setMoveDataTimeRanges] = useState([]);
|
||||||
|
const [moveDataTimeRangesType, setMoveDataTimeRangesType] = useState([]);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const [loadingTopic, setLoadingTopic] = useState<boolean>(true);
|
const [loadingTopic, setLoadingTopic] = useState<boolean>(true);
|
||||||
@@ -142,8 +144,23 @@ export default (props: DefaultConfig) => {
|
|||||||
title: '迁移数据时间范围',
|
title: '迁移数据时间范围',
|
||||||
dataIndex: 'newRetentionMs',
|
dataIndex: 'newRetentionMs',
|
||||||
render: (v: any, r: any, i: number) => {
|
render: (v: any, r: any, i: number) => {
|
||||||
|
const selectAfter = (
|
||||||
|
<Select
|
||||||
|
onChange={(n: any) => {
|
||||||
|
const moveDataTimeRangesCopyType = JSON.parse(JSON.stringify(moveDataTimeRangesType));
|
||||||
|
moveDataTimeRangesCopyType[i] = n === 'h' ? 1 : 60;
|
||||||
|
setMoveDataTimeRangesType(moveDataTimeRangesCopyType);
|
||||||
|
}}
|
||||||
|
defaultValue="h"
|
||||||
|
style={{ width: 82 }}
|
||||||
|
>
|
||||||
|
<Option value="m">Minute</Option>
|
||||||
|
<Option value="h">Hour</Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<InputNumber
|
<InputNumber
|
||||||
|
width={80}
|
||||||
min={0}
|
min={0}
|
||||||
max={99999}
|
max={99999}
|
||||||
defaultValue={moveDataTimeRanges[i]}
|
defaultValue={moveDataTimeRanges[i]}
|
||||||
@@ -153,8 +170,10 @@ export default (props: DefaultConfig) => {
|
|||||||
moveDataTimeRangesCopy[i] = n;
|
moveDataTimeRangesCopy[i] = n;
|
||||||
setMoveDataTimeRanges(moveDataTimeRangesCopy);
|
setMoveDataTimeRanges(moveDataTimeRangesCopy);
|
||||||
}}
|
}}
|
||||||
formatter={(value) => (value ? `${value} h` : '')}
|
className={'move-dete-time-tanges'}
|
||||||
parser={(value) => value.replace('h', '')}
|
// formatter={(value) => (value ? `${value} h` : '')}
|
||||||
|
// parser={(value) => value.replace('h', '')}
|
||||||
|
addonAfter={selectAfter}
|
||||||
></InputNumber>
|
></InputNumber>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -319,8 +338,7 @@ export default (props: DefaultConfig) => {
|
|||||||
drawerVisible &&
|
drawerVisible &&
|
||||||
Utils.request(Api.getTopicMetaData(+routeParams.clusterId))
|
Utils.request(Api.getTopicMetaData(+routeParams.clusterId))
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
const filterRes = res.filter((item: any) => item.type !== 1);
|
const topics = (res || []).map((item: any) => {
|
||||||
const topics = (filterRes || []).map((item: any) => {
|
|
||||||
return {
|
return {
|
||||||
label: item.topicName,
|
label: item.topicName,
|
||||||
value: item.topicName,
|
value: item.topicName,
|
||||||
@@ -402,7 +420,7 @@ export default (props: DefaultConfig) => {
|
|||||||
originalBrokerIdList: taskPlanData[index].currentBrokerIdList,
|
originalBrokerIdList: taskPlanData[index].currentBrokerIdList,
|
||||||
reassignBrokerIdList: taskPlanData[index].reassignBrokerIdList,
|
reassignBrokerIdList: taskPlanData[index].reassignBrokerIdList,
|
||||||
originalRetentionTimeUnitMs: topicData[index].retentionMs,
|
originalRetentionTimeUnitMs: topicData[index].retentionMs,
|
||||||
reassignRetentionTimeUnitMs: moveDataTimeRanges[index] * 60 * 60 * 1000,
|
reassignRetentionTimeUnitMs: (moveDataTimeRanges[index] * 60 * 60 * 1000) / (moveDataTimeRangesType[index] || 1),
|
||||||
latestDaysAvgBytesInList: topicData[index].latestDaysAvgBytesInList,
|
latestDaysAvgBytesInList: topicData[index].latestDaysAvgBytesInList,
|
||||||
latestDaysMaxBytesInList: topicData[index].latestDaysMaxBytesInList,
|
latestDaysMaxBytesInList: topicData[index].latestDaysMaxBytesInList,
|
||||||
partitionPlanList: taskPlanData[index].partitionPlanList,
|
partitionPlanList: taskPlanData[index].partitionPlanList,
|
||||||
@@ -476,6 +494,19 @@ export default (props: DefaultConfig) => {
|
|||||||
setTopicSelectValue(v);
|
setTopicSelectValue(v);
|
||||||
}}
|
}}
|
||||||
options={topicMetaData}
|
options={topicMetaData}
|
||||||
|
// 点击Tooltip会触发Select的下拉
|
||||||
|
// maxTagPlaceholder={(v) => {
|
||||||
|
// const tooltipValue = v
|
||||||
|
// .map((item) => {
|
||||||
|
// return item.value;
|
||||||
|
// })
|
||||||
|
// .join('、');
|
||||||
|
// return (
|
||||||
|
// <Tooltip visible={true} placement="topLeft" key={tooltipValue} title={tooltipValue}>
|
||||||
|
// <span>{'+' + v.length + '...'}</span>
|
||||||
|
// </Tooltip>
|
||||||
|
// );
|
||||||
|
// }}
|
||||||
></Select>
|
></Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -64,11 +64,6 @@
|
|||||||
.task-form {
|
.task-form {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcloud-select-selector {
|
|
||||||
max-height: 100px;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-task-plan-drawer {
|
.preview-task-plan-drawer {
|
||||||
@@ -80,4 +75,18 @@
|
|||||||
background: #F8F9FA;
|
background: #F8F9FA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-dete-time-tanges{
|
||||||
|
.dcloud-input-number-input-wrap{
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
.dcloud-input-number-wrapper{
|
||||||
|
.dcloud-select-selector{
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
background-color: inherit !important;
|
||||||
|
background: #F8F9FA;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,19 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
export const CHART_COLOR_LIST = [
|
export const CHART_COLOR_LIST = [
|
||||||
'#657DFC',
|
'#556ee6',
|
||||||
'#A7B1EB',
|
|
||||||
'#2AC8E4',
|
|
||||||
'#9DDEEB',
|
|
||||||
'#3991FF',
|
|
||||||
'#94BEF2',
|
'#94BEF2',
|
||||||
|
'#95e7ff',
|
||||||
|
'#9DDEEB',
|
||||||
|
'#A7B1EB',
|
||||||
'#C2D0E3',
|
'#C2D0E3',
|
||||||
'#F5B6B3',
|
|
||||||
'#85C80D',
|
|
||||||
'#C9E795',
|
|
||||||
'#A76CEC',
|
|
||||||
'#CCABF1',
|
'#CCABF1',
|
||||||
'#FF9C1B',
|
|
||||||
'#F5C993',
|
|
||||||
'#FFC300',
|
|
||||||
'#F9D77B',
|
'#F9D77B',
|
||||||
'#12CA7A',
|
'#F5C993',
|
||||||
'#8BA3C4',
|
|
||||||
'#FF7066',
|
|
||||||
'#A7E6C7',
|
'#A7E6C7',
|
||||||
'#F19FC9',
|
'#F19FC9',
|
||||||
'#AEAEAE',
|
'#F5B6B3',
|
||||||
'#D1D1D1',
|
'#C9E795',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const UNIT_MAP = {
|
export const UNIT_MAP = {
|
||||||
|
|||||||
@@ -12,20 +12,6 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
name: 'cluster',
|
name: 'cluster',
|
||||||
path: 'cluster',
|
path: 'cluster',
|
||||||
icon: 'icon-Cluster',
|
icon: 'icon-Cluster',
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'overview',
|
|
||||||
path: '',
|
|
||||||
icon: '#icon-luoji',
|
|
||||||
},
|
|
||||||
process.env.BUSINESS_VERSION
|
|
||||||
? {
|
|
||||||
name: 'balance',
|
|
||||||
path: 'balance',
|
|
||||||
icon: '#icon-luoji',
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
].filter((m) => m),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'broker',
|
name: 'broker',
|
||||||
@@ -83,6 +69,25 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
// },
|
// },
|
||||||
// ],
|
// ],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'operation',
|
||||||
|
path: 'operation',
|
||||||
|
icon: 'icon-Jobs',
|
||||||
|
children: [
|
||||||
|
process.env.BUSINESS_VERSION
|
||||||
|
? {
|
||||||
|
name: 'balance',
|
||||||
|
path: 'balance',
|
||||||
|
icon: '#icon-luoji',
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
{
|
||||||
|
name: 'jobs',
|
||||||
|
path: 'jobs',
|
||||||
|
icon: 'icon-Jobs',
|
||||||
|
},
|
||||||
|
].filter((m) => m),
|
||||||
|
},
|
||||||
process.env.BUSINESS_VERSION
|
process.env.BUSINESS_VERSION
|
||||||
? {
|
? {
|
||||||
name: 'produce-consume',
|
name: 'produce-consume',
|
||||||
@@ -127,11 +132,6 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
// path: 'acls',
|
// path: 'acls',
|
||||||
// icon: 'icon-wodegongzuotai',
|
// icon: 'icon-wodegongzuotai',
|
||||||
// },
|
// },
|
||||||
{
|
|
||||||
name: 'jobs',
|
|
||||||
path: 'jobs',
|
|
||||||
icon: 'icon-Jobs',
|
|
||||||
},
|
|
||||||
].filter((m) => m),
|
].filter((m) => m),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -258,3 +258,25 @@ li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
.img {
|
||||||
|
width: 51px;
|
||||||
|
height: 34px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
background-size: cover;
|
||||||
|
background-image: url('./assets/empty.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #919aac;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ export default {
|
|||||||
[`menu.${systemKey}.consumer-group.operating-state`]: 'Operating State',
|
[`menu.${systemKey}.consumer-group.operating-state`]: 'Operating State',
|
||||||
[`menu.${systemKey}.consumer-group.group-list`]: 'GroupList',
|
[`menu.${systemKey}.consumer-group.group-list`]: 'GroupList',
|
||||||
|
|
||||||
|
[`menu.${systemKey}.operation`]: 'Operation',
|
||||||
|
[`menu.${systemKey}.operation.balance`]: 'Load Rebalance',
|
||||||
|
[`menu.${systemKey}.operation.jobs`]: 'Job',
|
||||||
|
|
||||||
[`menu.${systemKey}.acls`]: 'ACLs',
|
[`menu.${systemKey}.acls`]: 'ACLs',
|
||||||
|
|
||||||
[`menu.${systemKey}.jobs`]: 'Job',
|
[`menu.${systemKey}.jobs`]: 'Job',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, IconFont, Divider, message } from 'knowdesign';
|
import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, IconFont, Divider, message } from 'knowdesign';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import Api from '@src/api';
|
import Api from '@src/api';
|
||||||
@@ -31,6 +31,10 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
form.setFieldsValue(props.record);
|
||||||
|
}, [props.record]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
title={
|
title={
|
||||||
@@ -44,6 +48,7 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
visible={props.visible}
|
visible={props.visible}
|
||||||
onClose={() => props.setVisible(false)}
|
onClose={() => props.setVisible(false)}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
destroyOnClose
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button size="small" onClick={onClose}>
|
<Button size="small" onClick={onClose}>
|
||||||
@@ -70,7 +75,7 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
{props.record?.documentation || '-'}
|
{props.record?.documentation || '-'}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Form form={form} layout="vertical" initialValues={props.record}>
|
<Form form={form} layout="vertical">
|
||||||
<Form.Item name="defaultValue" label="Kafka默认配置">
|
<Form.Item name="defaultValue" label="Kafka默认配置">
|
||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -14,19 +14,49 @@ export const getBrokerListColumns = (arg?: any) => {
|
|||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
render: (t: number, r: any) => {
|
render: (t: number, r: any) => {
|
||||||
return r?.alive ? (
|
return r?.alive ? (
|
||||||
<a
|
<>
|
||||||
onClick={() => {
|
<a
|
||||||
window.location.hash = `brokerId=${t || t === 0 ? t : ''}&host=${r.host || ''}`;
|
onClick={() => {
|
||||||
}}
|
window.location.hash = `brokerId=${t || t === 0 ? t : ''}&host=${r.host || ''}`;
|
||||||
>
|
}}
|
||||||
{t}
|
>
|
||||||
</a>
|
{t}
|
||||||
|
</a>
|
||||||
|
{r?.kafkaRoleList?.includes('controller') && (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: '#556EE6',
|
||||||
|
padding: '2px 5px',
|
||||||
|
background: '#eff1fd',
|
||||||
|
marginLeft: '4px',
|
||||||
|
transform: 'scale(0.83,0.83)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Controller
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span>{t}</span>
|
<>
|
||||||
|
<span>{t}</span>
|
||||||
|
{r?.kafkaRoleList?.includes('controller') && (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: '#556EE6',
|
||||||
|
padding: '2px 5px',
|
||||||
|
background: '#eff1fd',
|
||||||
|
marginLeft: '4px',
|
||||||
|
transform: 'scale(0.83,0.83)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Controller
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 120,
|
width: 150,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: 'Rack',
|
// title: 'Rack',
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import { goLogin } from '@src/constants/axiosConfig';
|
|||||||
// 权限对应表
|
// 权限对应表
|
||||||
export enum ClustersPermissionMap {
|
export enum ClustersPermissionMap {
|
||||||
CLUSTERS_MANAGE = '多集群管理',
|
CLUSTERS_MANAGE = '多集群管理',
|
||||||
|
CLUSTERS_MANAGE_VIEW = '多集群管理查看',
|
||||||
// Cluster
|
// Cluster
|
||||||
CLUSTER_ADD = '接入集群',
|
CLUSTER_ADD = '接入集群',
|
||||||
CLUSTER_DEL = '删除集群',
|
CLUSTER_DEL = '删除集群',
|
||||||
CLUSTER_CHANGE_HEALTHY = 'Cluster-修改健康规则',
|
CLUSTER_CHANGE_HEALTHY = 'Cluster-修改健康规则',
|
||||||
CLUSTER_CHANGE_INFO = 'Cluster-修改集群信息',
|
CLUSTER_CHANGE_INFO = 'Cluster-修改集群信息',
|
||||||
|
// LoadReBalance
|
||||||
|
REBALANCE_CYCLE = 'Cluster-LoadReBalance-周期均衡',
|
||||||
|
REBALANCE_IMMEDIATE = 'Cluster-LoadReBalance-立即均衡',
|
||||||
|
REBALANCE_SETTING = 'Cluster-LoadReBalance-设置集群规格',
|
||||||
// Broker
|
// Broker
|
||||||
BROKER_CHANGE_CONFIG = 'Broker-修改Broker配置',
|
BROKER_CHANGE_CONFIG = 'Broker-修改Broker配置',
|
||||||
// Topic
|
// Topic
|
||||||
@@ -19,6 +24,8 @@ export enum ClustersPermissionMap {
|
|||||||
TOPIC_DEL = 'Topic-删除Topic',
|
TOPIC_DEL = 'Topic-删除Topic',
|
||||||
TOPIC_EXPOND = 'Topic-扩分区',
|
TOPIC_EXPOND = 'Topic-扩分区',
|
||||||
TOPIC_ADD = 'Topic-新增Topic',
|
TOPIC_ADD = 'Topic-新增Topic',
|
||||||
|
TOPIC_MOVE_REPLICA = 'Topic-迁移副本',
|
||||||
|
TOPIC_CHANGE_REPLICA = 'Topic-扩缩副本',
|
||||||
// Consumers
|
// Consumers
|
||||||
CONSUMERS_RESET_OFFSET = 'Consumers-重置Offset',
|
CONSUMERS_RESET_OFFSET = 'Consumers-重置Offset',
|
||||||
// Test
|
// Test
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button, DatePicker, Drawer, Form, notification, Radio, Utils, Space, Divider } from 'knowdesign';
|
import { Button, DatePicker, Drawer, Form, notification, Radio, Utils, Space, Divider, message } from 'knowdesign';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import EditTable from '../TestingProduce/component/EditTable';
|
import EditTable from '../TestingProduce/component/EditTable';
|
||||||
import Api from '@src/api/index';
|
import Api from '@src/api/index';
|
||||||
@@ -53,11 +53,28 @@ export default (props: any) => {
|
|||||||
const [resetOffsetVisible, setResetOffsetVisible] = useState(false);
|
const [resetOffsetVisible, setResetOffsetVisible] = useState(false);
|
||||||
const customFormRef: any = React.createRef();
|
const customFormRef: any = React.createRef();
|
||||||
const clusterPhyId = Number(routeParams.clusterId);
|
const clusterPhyId = Number(routeParams.clusterId);
|
||||||
|
const [partitionIdList, setPartitionIdList] = useState([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
resetType: defaultResetType,
|
resetType: defaultResetType,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Utils.request(Api.getTopicsMetaData(record?.topicName, +routeParams.clusterId))
|
||||||
|
.then((res: any) => {
|
||||||
|
const partitionLists = (res?.partitionIdList || []).map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item,
|
||||||
|
value: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setPartitionIdList(partitionLists);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error(err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
let tableData;
|
let tableData;
|
||||||
if (customFormRef.current) {
|
if (customFormRef.current) {
|
||||||
@@ -160,8 +177,9 @@ export default (props: any) => {
|
|||||||
colCustomConfigs={[
|
colCustomConfigs={[
|
||||||
{
|
{
|
||||||
title: 'PartitionID',
|
title: 'PartitionID',
|
||||||
inputType: 'number',
|
inputType: 'select',
|
||||||
placeholder: '请输入Partition',
|
placeholder: '请输入Partition',
|
||||||
|
options: partitionIdList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Offset',
|
title: 'Offset',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const AutoPage = (props: any) => {
|
|||||||
|
|
||||||
const searchFn = () => {
|
const searchFn = () => {
|
||||||
const params: getOperatingStateListParams = {
|
const params: getOperatingStateListParams = {
|
||||||
pageNo: pageIndex,
|
pageNo: 1,
|
||||||
pageSize,
|
pageSize,
|
||||||
fuzzySearchDTOList: [],
|
fuzzySearchDTOList: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ const columns: any = [
|
|||||||
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
||||||
return (
|
return (
|
||||||
<div className="message-size">
|
<div className="message-size">
|
||||||
<Tooltip title={(movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'}>
|
<Tooltip
|
||||||
|
title={(movedSize === 0 && totalSize === 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'}
|
||||||
|
>
|
||||||
<Progress
|
<Progress
|
||||||
percent={movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
percent={movedSize === 0 && totalSize === 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
||||||
strokeColor="#556EE6"
|
strokeColor="#556EE6"
|
||||||
trailColor="#ECECF1"
|
trailColor="#ECECF1"
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
|
|||||||
@@ -237,12 +237,12 @@ const RebalancePlan = (props: PropsType) => {
|
|||||||
<Descriptions.Item labelStyle={{ width: '100px' }} label="迁移副本数">
|
<Descriptions.Item labelStyle={{ width: '100px' }} label="迁移副本数">
|
||||||
{data?.replicas || '-'}
|
{data?.replicas || '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="均衡阈值">
|
<Descriptions.Item label="均衡区间">
|
||||||
{data?.clusterBalanceIntervalList
|
{data?.clusterBalanceIntervalList
|
||||||
? data?.clusterBalanceIntervalList?.map((item: any) => {
|
? data?.clusterBalanceIntervalList?.map((item: any) => {
|
||||||
return (
|
return (
|
||||||
<Tag style={{ padding: '4px 8px', backgroundColor: 'rgba(33,37,41,0.08)', marginRight: '4px' }} key={item?.priority}>
|
<Tag style={{ padding: '4px 5px', backgroundColor: 'rgba(33,37,41,0.08)', marginRight: '4px' }} key={item?.priority}>
|
||||||
{item.type + ':' + item.intervalPercent + '%'}
|
{item.type?.slice(0, 1).toUpperCase() + item.type?.slice(1) + ':' + ' ±' + item.intervalPercent + '%'}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -314,9 +314,13 @@ export const getTaskDetailsColumns = (arg?: any) => {
|
|||||||
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
||||||
return (
|
return (
|
||||||
<div className="message-size">
|
<div className="message-size">
|
||||||
<Tooltip title={(movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'}>
|
<Tooltip
|
||||||
|
title={
|
||||||
|
(r.success === r.total && r.total > 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'
|
||||||
|
}
|
||||||
|
>
|
||||||
<Progress
|
<Progress
|
||||||
percent={movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
percent={r.success === r.total && r.total > 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
||||||
strokeColor="#556EE6"
|
strokeColor="#556EE6"
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
/>
|
/>
|
||||||
@@ -438,9 +442,13 @@ export const getMoveBalanceColumns = (arg?: any) => {
|
|||||||
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
const totalSize = r.totalSize ? Number(Utils.formatAssignSize(t, 'MB')) : 0;
|
||||||
return (
|
return (
|
||||||
<div className="message-size">
|
<div className="message-size">
|
||||||
<Tooltip title={(movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'}>
|
<Tooltip
|
||||||
|
title={
|
||||||
|
(r.success === r.total && r.total > 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0) + '%'
|
||||||
|
}
|
||||||
|
>
|
||||||
<Progress
|
<Progress
|
||||||
percent={movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
percent={r.success === r.total && r.total > 0 ? 100 : movedSize > 0 && totalSize > 0 ? (movedSize / totalSize) * 100 : 0}
|
||||||
strokeColor="#556EE6"
|
strokeColor="#556EE6"
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ const JobsList: React.FC = (props: any) => {
|
|||||||
tableProps={{
|
tableProps={{
|
||||||
tableId: 'jobs_list',
|
tableId: 'jobs_list',
|
||||||
showHeader: false,
|
showHeader: false,
|
||||||
rowKey: 'jobs_list',
|
rowKey: 'id',
|
||||||
loading: loading,
|
loading: loading,
|
||||||
columns: getJobsListColumns({ onDelete, setViewProgress }),
|
columns: getJobsListColumns({ onDelete, setViewProgress }),
|
||||||
dataSource: data,
|
dataSource: data,
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
if (formData && Object.keys(formData).length > 0) {
|
if (formData && Object.keys(formData).length > 0) {
|
||||||
console.log(formData, '有FormData');
|
|
||||||
const tableData = formData?.clusterBalanceIntervalList?.map((item: any) => {
|
const tableData = formData?.clusterBalanceIntervalList?.map((item: any) => {
|
||||||
const finfIndex = BalancedDimensions.findIndex((item1) => item1?.value === item?.type);
|
const finfIndex = BalancedDimensions.findIndex((item1) => item1?.value === item?.type);
|
||||||
return {
|
return {
|
||||||
@@ -201,7 +200,6 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
priority: index + 1,
|
priority: index + 1,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
console.log(res, '表单回显立即均衡');
|
|
||||||
setTableData(res);
|
setTableData(res);
|
||||||
setDimension(['disk', 'bytesIn', 'bytesOut']);
|
setDimension(['disk', 'bytesIn', 'bytesOut']);
|
||||||
setNodeTargetKeys([]);
|
setNodeTargetKeys([]);
|
||||||
@@ -220,14 +218,12 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
throttleUnitB: values?.throttleUnitM * 1024 * 1024,
|
throttleUnitB: values?.throttleUnitM * 1024 * 1024,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isCycle) {
|
if (values?.priority === 'throughput') {
|
||||||
if (values?.priority === 'throughput') {
|
params.parallelNum = 0;
|
||||||
params.parallelNum = 0;
|
params.executionStrategy = 1;
|
||||||
params.executionStrategy = 1;
|
} else if (values?.priority === 'stability') {
|
||||||
} else if (values?.priority === 'stability') {
|
params.parallelNum = 1;
|
||||||
params.parallelNum = 1;
|
params.executionStrategy = 2;
|
||||||
params.executionStrategy = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData?.jobId) {
|
if (formData?.jobId) {
|
||||||
@@ -382,6 +378,8 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
|
|
||||||
const drawerClose = (isArg?: boolean) => {
|
const drawerClose = (isArg?: boolean) => {
|
||||||
isArg ? onClose(isArg) : onClose();
|
isArg ? onClose(isArg) : onClose();
|
||||||
|
setParallelNum(0);
|
||||||
|
setExecutionStrategy(1);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -540,17 +538,38 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<h6 className="form-title">运行配置</h6>
|
<h6 className="form-title">运行配置</h6>
|
||||||
{!isCycle && (
|
{isCycle && (
|
||||||
<Form.Item label="" name="priority" rules={[{ required: true, message: 'Principle 不能为空' }]} initialValue="throughput">
|
<Form.Item
|
||||||
<Radio.Group onChange={priorityChange}>
|
className="schedule-cron"
|
||||||
<Radio value="throughput">吞吐量优先</Radio>
|
name="scheduleCron"
|
||||||
<Radio value="stability">稳定性优先</Radio>
|
label="任务周期"
|
||||||
<Radio value="custom">自定义</Radio>
|
rules={[
|
||||||
</Radio.Group>
|
{
|
||||||
|
required: true,
|
||||||
|
message: `请输入!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (_, value) => {
|
||||||
|
const valArr = value.split(' ');
|
||||||
|
if (valArr[1] === '*' || valArr[2] === '*') {
|
||||||
|
return Promise.reject(new Error('任务周期必须指定分钟、小时'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CronInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
<Form.Item label="" name="priority" rules={[{ required: true, message: 'Principle 不能为空' }]} initialValue="throughput">
|
||||||
{!isCycle && (
|
<Radio.Group onChange={priorityChange}>
|
||||||
|
<Radio value="throughput">吞吐量优先</Radio>
|
||||||
|
<Radio value="stability">稳定性优先</Radio>
|
||||||
|
<Radio value="custom">自定义</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
{
|
||||||
<Form.Item dependencies={['priority']} style={{ marginBottom: 0 }}>
|
<Form.Item dependencies={['priority']} style={{ marginBottom: 0 }}>
|
||||||
{({ getFieldValue }) =>
|
{({ getFieldValue }) =>
|
||||||
getFieldValue('priority') === 'custom' ? (
|
getFieldValue('priority') === 'custom' ? (
|
||||||
@@ -600,9 +619,9 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
}
|
||||||
|
|
||||||
{isCycle && (
|
{/* {isCycle && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="parallelNum"
|
name="parallelNum"
|
||||||
label={
|
label={
|
||||||
@@ -622,9 +641,9 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
>
|
>
|
||||||
<InputNumber min={0} max={999} placeholder="请输入任务并行度" style={{ width: '100%' }} />
|
<InputNumber min={0} max={999} placeholder="请输入任务并行度" style={{ width: '100%' }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{isCycle && (
|
{/* {isCycle && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
className="schedule-cron"
|
className="schedule-cron"
|
||||||
name="scheduleCron"
|
name="scheduleCron"
|
||||||
@@ -647,9 +666,9 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
>
|
>
|
||||||
<CronInput />
|
<CronInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{isCycle && (
|
{/* {isCycle && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="executionStrategy"
|
name="executionStrategy"
|
||||||
label={
|
label={
|
||||||
@@ -672,7 +691,7 @@ const BalanceDrawer: React.FC<PropsType> = ({ onClose, visible, isCycle = false,
|
|||||||
<Radio value={2}>优先最小副本</Radio>
|
<Radio value={2}>优先最小副本</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="throttleUnitM"
|
name="throttleUnitM"
|
||||||
|
|||||||
@@ -45,24 +45,57 @@ const HistoryDrawer: React.FC<PropsType> = ({ onClose, visible }) => {
|
|||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: 'Disk均衡率',
|
title: (
|
||||||
|
<span>
|
||||||
|
Disk<span style={{ fontSize: '12px', color: '#74788D' }}>{'(已均衡丨未均衡)'}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
dataIndex: 'disk',
|
dataIndex: 'disk',
|
||||||
render: (text: any, row: any) => {
|
render: (text: any, row: any) => {
|
||||||
return `${row?.sub?.disk?.successNu} (已均衡) / ${row?.sub?.disk?.failedNu} (未均衡)`;
|
// return `${row?.sub?.disk?.successNu} 丨 ${row?.sub?.disk?.failedNu}`;
|
||||||
|
return (
|
||||||
|
<div className="balance-history-column">
|
||||||
|
<span>{row?.sub?.disk?.successNu}</span>
|
||||||
|
<span>丨</span>
|
||||||
|
<span>{row?.sub?.disk?.failedNu}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'BytesIn均衡率',
|
title: (
|
||||||
|
<span>
|
||||||
|
BytesIn<span style={{ fontSize: '12px', color: '#74788D' }}>{'(已均衡丨未均衡)'}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
dataIndex: 'bytesIn',
|
dataIndex: 'bytesIn',
|
||||||
render: (text: any, row: any) => {
|
render: (text: any, row: any) => {
|
||||||
return `${row?.sub?.bytesIn?.successNu} (已均衡) / ${row?.sub?.bytesIn?.failedNu} (未均衡)`;
|
// return `${row?.sub?.bytesIn?.successNu} 丨 ${row?.sub?.bytesIn?.failedNu}`;
|
||||||
|
return (
|
||||||
|
<div className="balance-history-column">
|
||||||
|
<span>{row?.sub?.bytesIn?.successNu}</span>
|
||||||
|
<span>丨</span>
|
||||||
|
<span>{row?.sub?.bytesIn?.failedNu}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'BytesOut均衡率',
|
title: (
|
||||||
|
<span>
|
||||||
|
BytesOut<span style={{ fontSize: '12px', color: '#74788D' }}>{'(已均衡丨未均衡)'}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
dataIndex: 'bytesOut',
|
dataIndex: 'bytesOut',
|
||||||
render: (text: any, row: any) => {
|
render: (text: any, row: any) => {
|
||||||
return `${row?.sub?.bytesOut?.successNu} (已均衡) / ${row?.sub?.bytesOut?.failedNu} (未均衡)`;
|
// return `${row?.sub?.bytesOut?.successNu} 丨 ${row?.sub?.bytesOut?.failedNu}`;
|
||||||
|
return (
|
||||||
|
<div className="balance-history-column">
|
||||||
|
<span>{row?.sub?.bytesOut?.successNu}</span>
|
||||||
|
<span>丨</span>
|
||||||
|
<span>{row?.sub?.bytesOut?.failedNu}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -124,7 +157,7 @@ const HistoryDrawer: React.FC<PropsType> = ({ onClose, visible }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onTableChange = (curPagination: any) => {
|
const onTableChange = (curPagination: any) => {
|
||||||
getList({ page: curPagination.current, size: curPagination.pageSize });
|
getList({ pageNo: curPagination.current, pageSize: curPagination.pageSize });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -143,3 +143,19 @@
|
|||||||
// margin: 0 !important;
|
// margin: 0 !important;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.balance-history-column{
|
||||||
|
display: flex;
|
||||||
|
&>span:nth-child(1){
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
&>span:nth-child(2){
|
||||||
|
color: #74788d;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
&>span:last-child{
|
||||||
|
width: 20px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import api from '../../api';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import LoadRebalanceCardBar from '@src/components/CardBar/LoadRebalanceCardBar';
|
import LoadRebalanceCardBar from '@src/components/CardBar/LoadRebalanceCardBar';
|
||||||
import { BalanceFilter } from './BalanceFilter';
|
import { BalanceFilter } from './BalanceFilter';
|
||||||
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
|
|
||||||
const Balance_Status_OPTIONS = [
|
const Balance_Status_OPTIONS = [
|
||||||
{
|
{
|
||||||
@@ -288,21 +289,17 @@ const LoadBalance: React.FC = (props: any) => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const balanceClick = (val: boolean = false) => {
|
const balanceClick = (val: boolean) => {
|
||||||
if (val) {
|
Utils.request(api.getBalanceForm(global?.clusterInfo?.id), {
|
||||||
Utils.request(api.getBalanceForm(global?.clusterInfo?.id), {
|
method: 'GET',
|
||||||
method: 'GET',
|
})
|
||||||
|
.then((res: any) => {
|
||||||
|
const dataDe = res || {};
|
||||||
|
setCircleFormData(dataDe);
|
||||||
})
|
})
|
||||||
.then((res: any) => {
|
.catch(() => {
|
||||||
const dataDe = res || {};
|
setCircleFormData(null);
|
||||||
setCircleFormData(dataDe);
|
});
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setCircleFormData(null);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setCircleFormData(null);
|
|
||||||
}
|
|
||||||
setIsCycle(val);
|
setIsCycle(val);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
@@ -365,19 +362,23 @@ const LoadBalance: React.FC = (props: any) => {
|
|||||||
value: searchValue,
|
value: searchValue,
|
||||||
onChange: setSearchValue,
|
onChange: setSearchValue,
|
||||||
placeholder: '请输入 Host',
|
placeholder: '请输入 Host',
|
||||||
style: { width: '210px' },
|
style: { width: '248px' },
|
||||||
maxLength: 128,
|
maxLength: 128,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" ghost onClick={() => setPlanVisible(true)}>
|
<Button type="primary" ghost onClick={() => setPlanVisible(true)}>
|
||||||
均衡历史
|
均衡历史
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" ghost onClick={() => balanceClick(true)}>
|
{global.hasPermission(ClustersPermissionMap.REBALANCE_CYCLE) && (
|
||||||
周期均衡
|
<Button type="primary" ghost onClick={() => balanceClick(true)}>
|
||||||
</Button>
|
周期均衡
|
||||||
<Button type="primary" onClick={() => balanceClick(false)}>
|
</Button>
|
||||||
立即均衡
|
)}
|
||||||
</Button>
|
{global.hasPermission(ClustersPermissionMap.REBALANCE_IMMEDIATE) && (
|
||||||
|
<Button type="primary" onClick={() => balanceClick(false)}>
|
||||||
|
立即均衡
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{filterList && filterList.length > 0 && (
|
{filterList && filterList.length > 0 && (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const carouselList = [
|
|||||||
<img className="carousel-eg-ctr-two-img img-one" src={egTwoContent} />
|
<img className="carousel-eg-ctr-two-img img-one" src={egTwoContent} />
|
||||||
<div className="carousel-eg-ctr-two-desc desc-one">
|
<div className="carousel-eg-ctr-two-desc desc-one">
|
||||||
<span>Github: </span>
|
<span>Github: </span>
|
||||||
<span>4K</span>
|
<span>5K</span>
|
||||||
<span>+ Star的项目 Know Streaming</span>
|
<span>+ Star的项目 Know Streaming</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="carousel-eg-ctr-two-desc desc-two">
|
<div className="carousel-eg-ctr-two-desc desc-two">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign';
|
import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import api from '../../api';
|
import api from '@src/api';
|
||||||
import { regClusterName, regUsername } from '../../constants/reg';
|
import { regClusterName, regUsername } from '@src/constants/reg';
|
||||||
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
|
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
|
||||||
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
||||||
|
|
||||||
@@ -21,40 +21,28 @@ word=\\"xxxxxx\\";"
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const AccessClusters = (props: any): JSX.Element => {
|
const AccessClusters = (props: any): JSX.Element => {
|
||||||
|
const { afterSubmitSuccess, clusterInfo, visible } = props;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const { afterSubmitSuccess, infoLoading, clusterInfo, visible } = props;
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [security, setSecurity] = React.useState(clusterInfo?.security || 'None');
|
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
|
||||||
|
const [security, setSecurity] = React.useState(curClusterInfo?.security || 'None');
|
||||||
const [extra, setExtra] = React.useState({
|
const [extra, setExtra] = React.useState({
|
||||||
versionExtra: '',
|
versionExtra: '',
|
||||||
zooKeeperExtra: '',
|
zooKeeperExtra: '',
|
||||||
bootstrapExtra: '',
|
bootstrapExtra: '',
|
||||||
jmxExtra: '',
|
jmxExtra: '',
|
||||||
});
|
});
|
||||||
const [isLowVersion, setIsLowVersion] = React.useState<any>(false);
|
const [isLowVersion, setIsLowVersion] = React.useState<boolean>(false);
|
||||||
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<any>(false);
|
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const lastFormItemValue = React.useRef({
|
const lastFormItemValue = React.useRef({
|
||||||
bootstrap: clusterInfo?.bootstrapServers || '',
|
bootstrap: curClusterInfo?.bootstrapServers || '',
|
||||||
zookeeper: clusterInfo?.zookeeper || '',
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
clientProperties: clusterInfo?.clientProperties || {},
|
clientProperties: curClusterInfo?.clientProperties || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion);
|
|
||||||
lastFormItemValue.current.bootstrap = clusterInfo?.bootstrapServers || '';
|
|
||||||
lastFormItemValue.current.zookeeper = clusterInfo?.zookeeper || '';
|
|
||||||
lastFormItemValue.current.clientProperties = clusterInfo?.clientProperties || {};
|
|
||||||
setIsLowVersion(showLowVersion);
|
|
||||||
setExtra({
|
|
||||||
...extra,
|
|
||||||
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
|
|
||||||
});
|
|
||||||
form.setFieldsValue({ ...clusterInfo });
|
|
||||||
}, [clusterInfo]);
|
|
||||||
|
|
||||||
const onHandleValuesChange = (value: any, allValues: any) => {
|
const onHandleValuesChange = (value: any, allValues: any) => {
|
||||||
Object.keys(value).forEach((key) => {
|
Object.keys(value).forEach((key) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -128,10 +116,10 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
zookeeper: res.zookeeper || '',
|
zookeeper: res.zookeeper || '',
|
||||||
};
|
};
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!isNaN(clusterInfo?.id)) {
|
if (!isNaN(curClusterInfo?.id)) {
|
||||||
Utils.put(api.phyCluster, {
|
Utils.put(api.phyCluster, {
|
||||||
...params,
|
...params,
|
||||||
id: clusterInfo?.id,
|
id: curClusterInfo?.id,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message.success('编辑成功');
|
message.success('编辑成功');
|
||||||
@@ -219,7 +207,11 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 如果kafkaVersion小于最低版本则提示
|
// 如果kafkaVersion小于最低版本则提示
|
||||||
const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion);
|
const showLowVersion = !(
|
||||||
|
curClusterInfo?.zookeeper ||
|
||||||
|
!curClusterInfo?.kafkaVersion ||
|
||||||
|
curClusterInfo?.kafkaVersion >= lowKafkaVersion
|
||||||
|
);
|
||||||
setIsLowVersion(showLowVersion);
|
setIsLowVersion(showLowVersion);
|
||||||
setExtra({
|
setExtra({
|
||||||
...extraMsg,
|
...extraMsg,
|
||||||
@@ -232,6 +224,55 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const showLowVersion = !(curClusterInfo?.zookeeper || !curClusterInfo?.kafkaVersion || curClusterInfo?.kafkaVersion >= lowKafkaVersion);
|
||||||
|
lastFormItemValue.current = {
|
||||||
|
bootstrap: curClusterInfo?.bootstrapServers || '',
|
||||||
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
|
clientProperties: curClusterInfo?.clientProperties || {},
|
||||||
|
};
|
||||||
|
setIsLowVersion(showLowVersion);
|
||||||
|
setExtra({
|
||||||
|
...extra,
|
||||||
|
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
|
||||||
|
});
|
||||||
|
form.setFieldsValue({ ...curClusterInfo });
|
||||||
|
}, [curClusterInfo]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
if (clusterInfo?.id) {
|
||||||
|
setLoading(true);
|
||||||
|
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
|
||||||
|
.then((res: any) => {
|
||||||
|
let jmxProperties = null;
|
||||||
|
try {
|
||||||
|
jmxProperties = JSON.parse(res?.jmxProperties);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转化值对应成表单值
|
||||||
|
if (jmxProperties?.openSSL) {
|
||||||
|
jmxProperties.security = 'Password';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jmxProperties) {
|
||||||
|
res = Object.assign({}, res || {}, jmxProperties);
|
||||||
|
}
|
||||||
|
setCurClusterInfo(res);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, clusterInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -256,16 +297,8 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
placement="right"
|
placement="right"
|
||||||
width={480}
|
width={480}
|
||||||
>
|
>
|
||||||
<Spin spinning={loading || !!infoLoading}>
|
<Spin spinning={loading}>
|
||||||
<Form
|
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
||||||
form={form}
|
|
||||||
initialValues={{
|
|
||||||
security,
|
|
||||||
...clusterInfo,
|
|
||||||
}}
|
|
||||||
layout="vertical"
|
|
||||||
onValuesChange={onHandleValuesChange}
|
|
||||||
>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label="集群名称"
|
label="集群名称"
|
||||||
@@ -277,11 +310,9 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('集群名称不能为空');
|
return Promise.reject('集群名称不能为空');
|
||||||
}
|
}
|
||||||
|
if (value === curClusterInfo?.name) {
|
||||||
if (value === clusterInfo?.name) {
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value?.length > 128) {
|
if (value?.length > 128) {
|
||||||
return Promise.reject('集群名称长度限制在1~128字符');
|
return Promise.reject('集群名称长度限制在1~128字符');
|
||||||
}
|
}
|
||||||
@@ -307,13 +338,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="bootstrapServers"
|
name="bootstrapServers"
|
||||||
label="Bootstrap Servers"
|
label="Bootstrap Servers"
|
||||||
extra={
|
extra={<span className={extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
|
||||||
extra.bootstrapExtra.includes('连接成功') ? (
|
|
||||||
<span>{extra.bootstrapExtra}</span>
|
|
||||||
) : (
|
|
||||||
<span className="error-extra-info">{extra.bootstrapExtra}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
validateTrigger={'onBlur'}
|
validateTrigger={'onBlur'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -349,13 +374,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="zookeeper"
|
name="zookeeper"
|
||||||
label="Zookeeper"
|
label="Zookeeper"
|
||||||
extra={
|
extra={<span className={extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
|
||||||
extra.zooKeeperExtra.includes('连接成功') ? (
|
|
||||||
<span>{extra.zooKeeperExtra}</span>
|
|
||||||
) : (
|
|
||||||
<span className="error-extra-info">{extra.zooKeeperExtra}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
||||||
validateTrigger={'onBlur'}
|
validateTrigger={'onBlur'}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -458,7 +477,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
style={{ width: '58%' }}
|
style={{ width: '58%' }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||||
validator: async (rule: any, value: string) => {
|
validator: async (rule: any, value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('用户名不能为空');
|
return Promise.reject('用户名不能为空');
|
||||||
@@ -483,7 +502,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
style={{ width: '38%', marginRight: 0 }}
|
style={{ width: '38%', marginRight: 0 }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||||
validator: async (rule: any, value: string) => {
|
validator: async (rule: any, value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('密码不能为空');
|
return Promise.reject('密码不能为空');
|
||||||
|
|||||||
@@ -1,102 +1,108 @@
|
|||||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||||
import { Checkbox } from 'knowdesign';
|
import { Checkbox } from 'knowdesign';
|
||||||
|
import { CheckboxValueType } from 'knowdesign/es/basic/checkbox/Group';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
|
|
||||||
interface IVersion {
|
|
||||||
firstLine: string[];
|
|
||||||
leftVersions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => {
|
const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => {
|
||||||
const { kafkaVersions, onChangeCheckGroup } = props;
|
const { kafkaVersions: newVersions, onChangeCheckGroup } = props;
|
||||||
const [checkedKafkaVersion, setCheckedKafkaVersion] = React.useState<IVersion>({
|
const [versions, setVersions] = React.useState<string[]>([]);
|
||||||
firstLine: [],
|
const [versionsState, setVersionsState] = React.useState<{
|
||||||
leftVersions: [],
|
[key: string]: boolean;
|
||||||
});
|
}>({});
|
||||||
const [allVersion, setAllVersion] = React.useState<IVersion>({
|
|
||||||
firstLine: [],
|
|
||||||
leftVersions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [indeterminate, setIndeterminate] = React.useState(false);
|
const [indeterminate, setIndeterminate] = React.useState(false);
|
||||||
const [checkAll, setCheckAll] = React.useState(true);
|
const [checkAll, setCheckAll] = React.useState(true);
|
||||||
const [moreGroupWidth, setMoreGroupWidth] = React.useState(400);
|
const [groupInfo, setGroupInfo] = useState({
|
||||||
|
width: 400,
|
||||||
|
num: 0,
|
||||||
|
});
|
||||||
const [showMore, setShowMore] = React.useState(false);
|
const [showMore, setShowMore] = React.useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('click', handleDocumentClick);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', handleDocumentClick);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDocumentClick = (e: Event) => {
|
const handleDocumentClick = (e: Event) => {
|
||||||
setShowMore(false);
|
setShowMore(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCheckAllStauts = (list: string[], otherList: string[]) => {
|
const updateGroupInfo = () => {
|
||||||
onChangeCheckGroup([...list, ...otherList]);
|
|
||||||
setIndeterminate(!!list.length && list.length + otherList.length < kafkaVersions.length);
|
|
||||||
setCheckAll(list.length + otherList.length === kafkaVersions.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTwoPanelVersion = () => {
|
|
||||||
const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth;
|
const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth;
|
||||||
const checkgroupWidth = width - 100 - 86;
|
const checkgroupWidth = width - 100 - 86;
|
||||||
const num = (checkgroupWidth / 108) | 0;
|
const num = (checkgroupWidth / 108) | 0;
|
||||||
const firstLine = Array.from(kafkaVersions).splice(0, num);
|
setGroupInfo({
|
||||||
setMoreGroupWidth(num * 108 + 88 + 66);
|
width: num * 108 + 88 + 66,
|
||||||
const leftVersions = Array.from(kafkaVersions).splice(num);
|
num,
|
||||||
return { firstLine, leftVersions };
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFirstVersionChange = (list: []) => {
|
const getCheckedList = (
|
||||||
setCheckedKafkaVersion({
|
versionState: {
|
||||||
...checkedKafkaVersion,
|
[key: string]: boolean;
|
||||||
firstLine: list,
|
},
|
||||||
});
|
filterFunc: (item: [string, boolean], i: number) => boolean
|
||||||
|
) => {
|
||||||
setCheckAllStauts(list, checkedKafkaVersion.leftVersions);
|
return Object.entries(versionState)
|
||||||
|
.filter(filterFunc)
|
||||||
|
.map(([key]) => key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLeftVersionChange = (list: []) => {
|
const onVersionsChange = (isFirstLine: boolean, list: CheckboxValueType[]) => {
|
||||||
setCheckedKafkaVersion({
|
const newVersionsState = { ...versionsState };
|
||||||
...checkedKafkaVersion,
|
Object.keys(newVersionsState).forEach((key, i) => {
|
||||||
leftVersions: list,
|
if (isFirstLine && i < groupInfo.num) {
|
||||||
|
newVersionsState[key] = list.includes(key);
|
||||||
|
} else if (!isFirstLine && i >= groupInfo.num) {
|
||||||
|
newVersionsState[key] = list.includes(key);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setCheckAllStauts(list, checkedKafkaVersion.firstLine);
|
const checkedLen = Object.values(newVersionsState).filter((v) => v).length;
|
||||||
|
|
||||||
|
setVersionsState(newVersionsState);
|
||||||
|
setIndeterminate(checkedLen && checkedLen < newVersions.length);
|
||||||
|
setCheckAll(checkedLen === newVersions.length);
|
||||||
|
onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCheckAllChange = (e: any) => {
|
const onCheckAllChange = (e: any) => {
|
||||||
const versions = getTwoPanelVersion();
|
const checked = e.target.checked;
|
||||||
|
const newVersionsState = { ...versionsState };
|
||||||
setCheckedKafkaVersion(
|
Object.keys(newVersionsState).forEach((key) => (newVersionsState[key] = checked));
|
||||||
e.target.checked
|
|
||||||
? versions
|
|
||||||
: {
|
|
||||||
firstLine: [],
|
|
||||||
leftVersions: [],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onChangeCheckGroup(e.target.checked ? [...versions.firstLine, ...versions.leftVersions] : []);
|
|
||||||
|
|
||||||
|
setVersionsState(newVersionsState);
|
||||||
setIndeterminate(false);
|
setIndeterminate(false);
|
||||||
setCheckAll(e.target.checked);
|
setCheckAll(checked);
|
||||||
|
onChangeCheckGroup(e.target.checked ? versions : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const handleVersionLine = () => {
|
const newVersionsState = { ...versionsState };
|
||||||
const versions = getTwoPanelVersion();
|
Object.keys(newVersionsState).forEach((key) => {
|
||||||
setAllVersion(versions);
|
if (!newVersions.includes(key)) {
|
||||||
setCheckedKafkaVersion(versions);
|
delete newVersionsState[key];
|
||||||
};
|
}
|
||||||
handleVersionLine();
|
});
|
||||||
|
newVersions.forEach((version) => {
|
||||||
|
if (!Object.keys(newVersionsState).includes(version)) {
|
||||||
|
newVersionsState[version] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const checkedLen = Object.values(newVersionsState).filter((v) => v).length;
|
||||||
|
|
||||||
window.addEventListener('resize', handleVersionLine); //监听窗口大小改变
|
setVersions([...newVersions]);
|
||||||
return () => window.removeEventListener('resize', debounce(handleVersionLine, 500));
|
setVersionsState(newVersionsState);
|
||||||
|
setIndeterminate(checkedLen && checkedLen < newVersions.length);
|
||||||
|
setCheckAll(checkedLen === newVersions.length);
|
||||||
|
onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state));
|
||||||
|
}, [newVersions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateGroupInfo();
|
||||||
|
const listen = debounce(updateGroupInfo, 500);
|
||||||
|
window.addEventListener('resize', listen); //监听窗口大小改变
|
||||||
|
document.addEventListener('click', handleDocumentClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', listen);
|
||||||
|
document.removeEventListener('click', handleDocumentClick);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,17 +113,21 @@ const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup:
|
|||||||
全选
|
全选
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<CheckboxGroup options={allVersion.firstLine} value={checkedKafkaVersion.firstLine} onChange={onFirstVersionChange} />
|
<CheckboxGroup
|
||||||
|
options={Array.from(versions).splice(0, groupInfo.num)}
|
||||||
|
value={getCheckedList(versionsState, ([, state], i) => i < groupInfo.num && state)}
|
||||||
|
onChange={(list) => onVersionsChange(true, list)}
|
||||||
|
/>
|
||||||
{showMore ? (
|
{showMore ? (
|
||||||
<CheckboxGroup
|
<CheckboxGroup
|
||||||
style={{ width: moreGroupWidth }}
|
style={{ width: groupInfo.width }}
|
||||||
className="more-check-group"
|
className="more-check-group"
|
||||||
options={allVersion.leftVersions}
|
options={Array.from(versions).splice(groupInfo.num)}
|
||||||
value={checkedKafkaVersion.leftVersions}
|
value={getCheckedList(versionsState, ([, state], i) => i >= groupInfo.num && state)}
|
||||||
onChange={onLeftVersionChange}
|
onChange={(list) => onVersionsChange(false, list)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{allVersion.leftVersions.length ? (
|
{versions.length > groupInfo.num ? (
|
||||||
<div className="more-btn" onClick={() => setShowMore(!showMore)}>
|
<div className="more-btn" onClick={() => setShowMore(!showMore)}>
|
||||||
<a>
|
<a>
|
||||||
{!showMore ? '展开更多' : '收起更多'} <DoubleRightOutlined style={{ transform: `rotate(${showMore ? '270' : '90'}deg)` }} />
|
{!showMore ? '展开更多' : '收起更多'} <DoubleRightOutlined style={{ transform: `rotate(${showMore ? '270' : '90'}deg)` }} />
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState, useReducer } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign';
|
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign';
|
||||||
import API from '../../api';
|
import API from '@src/api';
|
||||||
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { healthSorceList, linesMetric, pointsMetric, sortFieldList, sortTypes, statusFilters } from './config';
|
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
|
||||||
import { oneDayMillims } from '../../constants/common';
|
import ClusterList from './List';
|
||||||
import ListScroll from './List';
|
|
||||||
import AccessClusters from './AccessCluster';
|
import AccessClusters from './AccessCluster';
|
||||||
import CustomCheckGroup from './CustomCheckGroup';
|
import CustomCheckGroup from './CustomCheckGroup';
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
@@ -13,98 +12,85 @@ import { ClustersPermissionMap } from '../CommonConfig';
|
|||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
interface ClustersState {
|
||||||
|
liveCount: number;
|
||||||
|
downCount: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchParams {
|
||||||
|
healthScoreRange?: [number, number];
|
||||||
|
checkedKafkaVersions?: string[];
|
||||||
|
sortInfo?: {
|
||||||
|
sortField: string;
|
||||||
|
sortType: string;
|
||||||
|
};
|
||||||
|
keywords?: string;
|
||||||
|
clusterStatus?: number[];
|
||||||
|
isReloadAll?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未接入集群默认页
|
||||||
|
const DefaultPage = (props: { setVisible: (visible: boolean) => void }) => {
|
||||||
|
return (
|
||||||
|
<div className="empty-page">
|
||||||
|
<div className="title">Kafka 多集群管理</div>
|
||||||
|
<div className="img">
|
||||||
|
<div className="img-card-1" />
|
||||||
|
<div className="img-card-2" />
|
||||||
|
<div className="img-card-3" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button className="header-filter-top-button" type="primary" onClick={() => props.setVisible(true)}>
|
||||||
|
<span>
|
||||||
|
<IconFont type="icon-jiahao" />
|
||||||
|
<span className="text">接入集群</span>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const LoadingState = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<Spin spinning={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const MultiClusterPage = () => {
|
const MultiClusterPage = () => {
|
||||||
const [run, setRun] = useState<boolean>(false);
|
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const [statusList, setStatusList] = React.useState([1, 0]);
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
|
const [accessClusterVisible, setAccessClusterVisible] = React.useState(false);
|
||||||
|
const [curClusterInfo, setCurClusterInfo] = useState<any>({});
|
||||||
const [kafkaVersions, setKafkaVersions] = React.useState<string[]>([]);
|
const [kafkaVersions, setKafkaVersions] = React.useState<string[]>([]);
|
||||||
const [existKafkaVersion, setExistKafkaVersion] = React.useState<string[]>([]);
|
const [existKafkaVersion, setExistKafkaVersion] = React.useState<string[]>([]);
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [stateInfo, setStateInfo] = React.useState<ClustersState>({
|
||||||
const [list, setList] = useState<[]>([]);
|
|
||||||
const [healthScoreRange, setHealthScoreRange] = React.useState([0, 100]);
|
|
||||||
const [checkedKafkaVersions, setCheckedKafkaVersions] = React.useState<string[]>([]);
|
|
||||||
const [sortInfo, setSortInfo] = React.useState({
|
|
||||||
sortField: 'HealthScore',
|
|
||||||
sortType: 'asc',
|
|
||||||
});
|
|
||||||
const [clusterLoading, setClusterLoading] = useState(true);
|
|
||||||
const [pageLoading, setPageLoading] = useState(true);
|
|
||||||
const [isReload, setIsReload] = useState(false);
|
|
||||||
const [versionLoading, setVersionLoading] = useState(true);
|
|
||||||
const [searchKeywords, setSearchKeywords] = useState('');
|
|
||||||
const [stateInfo, setStateInfo] = React.useState({
|
|
||||||
downCount: 0,
|
downCount: 0,
|
||||||
liveCount: 0,
|
liveCount: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const [pagination, setPagination] = useState({
|
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
||||||
pageNo: 1,
|
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
||||||
pageSize: 10,
|
keywords: '',
|
||||||
total: 0,
|
checkedKafkaVersions: [],
|
||||||
|
healthScoreRange: [0, 100],
|
||||||
|
sortInfo: {
|
||||||
|
sortField: 'HealthScore',
|
||||||
|
sortType: 'asc',
|
||||||
|
},
|
||||||
|
clusterStatus: [0, 1],
|
||||||
|
// 是否拉取当前所有数据
|
||||||
|
isReloadAll: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchKeyword = useRef('');
|
const searchKeyword = useRef('');
|
||||||
|
const isReload = useRef(false);
|
||||||
|
|
||||||
const getPhyClustersDashbord = (pageNo: number, pageSize: number) => {
|
// 获取集群状态
|
||||||
const endTime = new Date().getTime();
|
|
||||||
const startTime = endTime - oneDayMillims;
|
|
||||||
const params = {
|
|
||||||
metricLines: {
|
|
||||||
endTime,
|
|
||||||
metricsNames: linesMetric,
|
|
||||||
startTime,
|
|
||||||
},
|
|
||||||
latestMetricNames: pointsMetric,
|
|
||||||
pageNo: pageNo || 1,
|
|
||||||
pageSize: pageSize || 10,
|
|
||||||
preciseFilterDTOList: [
|
|
||||||
{
|
|
||||||
fieldName: 'kafkaVersion',
|
|
||||||
fieldValueList: checkedKafkaVersions as (string | number)[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rangeFilterDTOList: [
|
|
||||||
{
|
|
||||||
fieldMaxValue: healthScoreRange[1],
|
|
||||||
fieldMinValue: healthScoreRange[0],
|
|
||||||
fieldName: 'HealthScore',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
searchKeywords,
|
|
||||||
...sortInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (statusList.length === 1) {
|
|
||||||
params.preciseFilterDTOList.push({
|
|
||||||
fieldName: 'Alive',
|
|
||||||
fieldValueList: statusList,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Utils.post(API.phyClustersDashbord, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSupportKafkaVersion = () => {
|
|
||||||
Utils.request(API.supportKafkaVersion).then((res) => {
|
|
||||||
setKafkaVersions(Object.keys(res || {}));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getExistKafkaVersion = () => {
|
|
||||||
setVersionLoading(true);
|
|
||||||
Utils.request(API.getClustersVersion)
|
|
||||||
.then((versions: string[]) => {
|
|
||||||
if (!Array.isArray(versions)) {
|
|
||||||
versions = [];
|
|
||||||
}
|
|
||||||
setExistKafkaVersion(versions.sort().reverse() || []);
|
|
||||||
setVersionLoading(false);
|
|
||||||
setCheckedKafkaVersions(versions || []);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setVersionLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPhyClusterState = () => {
|
const getPhyClusterState = () => {
|
||||||
Utils.request(API.phyClusterState)
|
Utils.request(API.phyClusterState)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
@@ -115,213 +101,224 @@ const MultiClusterPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取 kafka 全部版本
|
||||||
|
const getSupportKafkaVersion = () => {
|
||||||
|
Utils.request(API.supportKafkaVersion).then((res) => {
|
||||||
|
setKafkaVersions(Object.keys(res || {}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSearchParams = (params: SearchParams) => {
|
||||||
|
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchParamsChangeFunc = {
|
||||||
|
// 健康分改变
|
||||||
|
onSilderChange: (value: [number, number]) =>
|
||||||
|
updateSearchParams({
|
||||||
|
healthScoreRange: value,
|
||||||
|
}),
|
||||||
|
// 排序信息改变
|
||||||
|
onSortInfoChange: (type: string, value: string) =>
|
||||||
|
updateSearchParams({
|
||||||
|
sortInfo: {
|
||||||
|
...searchParams.sortInfo,
|
||||||
|
[type]: value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Live / Down 筛选
|
||||||
|
onClusterStatusChange: (list: number[]) =>
|
||||||
|
updateSearchParams({
|
||||||
|
clusterStatus: list,
|
||||||
|
}),
|
||||||
|
// 集群名称搜索项改变
|
||||||
|
onInputChange: () =>
|
||||||
|
updateSearchParams({
|
||||||
|
keywords: searchKeyword.current,
|
||||||
|
}),
|
||||||
|
// 集群版本筛选
|
||||||
|
onChangeCheckGroup: (list: string[]) => {
|
||||||
|
updateSearchParams({
|
||||||
|
checkedKafkaVersions: list,
|
||||||
|
isReloadAll: isReload.current,
|
||||||
|
});
|
||||||
|
isReload.current = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前接入集群的 kafka 版本
|
||||||
|
const getExistKafkaVersion = (isReloadAll = false) => {
|
||||||
|
isReload.current = isReloadAll;
|
||||||
|
Utils.request(API.getClustersVersion).then((versions: string[]) => {
|
||||||
|
if (!Array.isArray(versions)) {
|
||||||
|
versions = [];
|
||||||
|
}
|
||||||
|
setExistKafkaVersion(versions.sort().reverse() || []);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 接入/编辑集群
|
||||||
|
const showAccessCluster = (clusterInfo: any = {}) => {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
setAccessClusterVisible(true);
|
||||||
|
};
|
||||||
|
// 接入/编辑集群回调
|
||||||
|
const afterAccessCluster = () => {
|
||||||
|
getPhyClusterState();
|
||||||
|
getExistKafkaVersion(true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPhyClusterState();
|
getPhyClusterState();
|
||||||
getSupportKafkaVersion();
|
getSupportKafkaVersion();
|
||||||
getExistKafkaVersion();
|
getExistKafkaVersion();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!pageLoading && stateInfo.total) {
|
|
||||||
setRun(true);
|
|
||||||
}
|
|
||||||
}, [pageLoading, stateInfo]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (versionLoading) return;
|
|
||||||
setClusterLoading(true);
|
|
||||||
getPhyClustersDashbord(pagination.pageNo, pagination.pageSize)
|
|
||||||
.then((res: any) => {
|
|
||||||
setPagination(res.pagination);
|
|
||||||
setList(res?.bizData || []);
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setClusterLoading(false);
|
|
||||||
});
|
|
||||||
}, [sortInfo, checkedKafkaVersions, healthScoreRange, statusList, searchKeywords, isReload]);
|
|
||||||
|
|
||||||
const onSilderChange = (value: number[]) => {
|
|
||||||
setHealthScoreRange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectChange = (type: string, value: string) => {
|
|
||||||
setSortInfo({
|
|
||||||
...sortInfo,
|
|
||||||
[type]: value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onStatusChange = (list: []) => {
|
|
||||||
setStatusList(list);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInputChange = (e: any) => {
|
|
||||||
const { value } = e.target;
|
|
||||||
setSearchKeywords(value.trim());
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeCheckGroup = (list: []) => {
|
|
||||||
setCheckedKafkaVersions(list);
|
|
||||||
};
|
|
||||||
|
|
||||||
const afterSubmitSuccessAccessClusters = () => {
|
|
||||||
getPhyClusterState();
|
|
||||||
setIsReload(!isReload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderEmpty = () => {
|
|
||||||
return (
|
|
||||||
<div className="empty-page">
|
|
||||||
<div className="title">Kafka 多集群管理</div>
|
|
||||||
<div className="img">
|
|
||||||
<div className="img-card-1" />
|
|
||||||
<div className="img-card-2" />
|
|
||||||
<div className="img-card-3" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button className="header-filter-top-button" type="primary" onClick={() => setVisible(true)}>
|
|
||||||
<span>
|
|
||||||
<IconFont type="icon-jiahao" />
|
|
||||||
<span className="text">接入集群</span>
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderLoading = () => {
|
|
||||||
return (
|
|
||||||
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
||||||
<Spin spinning={true} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
return (
|
|
||||||
<div className="multi-cluster-page" id="scrollableDiv">
|
|
||||||
<div className="multi-cluster-page-fixed">
|
|
||||||
<div className="content-container">
|
|
||||||
<div className="multi-cluster-header">
|
|
||||||
<div className="cluster-header-card">
|
|
||||||
<div className="cluster-header-card-bg-left"></div>
|
|
||||||
<div className="cluster-header-card-bg-right"></div>
|
|
||||||
<h5 className="header-card-title">
|
|
||||||
Clusters<span className="chinese-text"> 总数</span>
|
|
||||||
</h5>
|
|
||||||
<div className="header-card-total">{stateInfo.total}</div>
|
|
||||||
<div className="header-card-info">
|
|
||||||
<div className="card-info-item card-info-item-live">
|
|
||||||
<div>
|
|
||||||
live
|
|
||||||
<span className="info-item-value">
|
|
||||||
<em>{stateInfo.liveCount}</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-info-item card-info-item-down">
|
|
||||||
<div>
|
|
||||||
down
|
|
||||||
<span className="info-item-value">
|
|
||||||
<em>{stateInfo.downCount}</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="cluster-header-filter">
|
|
||||||
<div className="header-filter-top">
|
|
||||||
<div className="header-filter-top-input">
|
|
||||||
<Input
|
|
||||||
onPressEnter={onInputChange}
|
|
||||||
onChange={(e) => (searchKeyword.current = e.target.value)}
|
|
||||||
allowClear
|
|
||||||
bordered={false}
|
|
||||||
placeholder="请输入ClusterName进行搜索"
|
|
||||||
suffix={<IconFont className="icon" type="icon-fangdajing" onClick={() => setSearchKeywords(searchKeyword.current)} />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? (
|
|
||||||
<>
|
|
||||||
<div className="header-filter-top-divider"></div>
|
|
||||||
<Button className="header-filter-top-button" type="primary" onClick={() => setVisible(true)}>
|
|
||||||
<IconFont type="icon-jiahao" />
|
|
||||||
<span className="text">接入集群</span>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="header-filter-bottom">
|
|
||||||
<div className="header-filter-bottom-item header-filter-bottom-item-checkbox">
|
|
||||||
<h3 className="header-filter-bottom-item-title">版本分布</h3>
|
|
||||||
<div className="header-filter-bottom-item-content flex">
|
|
||||||
{existKafkaVersion.length ? (
|
|
||||||
<CustomCheckGroup kafkaVersions={existKafkaVersion} onChangeCheckGroup={onChangeCheckGroup} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
|
||||||
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
|
||||||
<div className="header-filter-bottom-item-content">
|
|
||||||
<Slider range step={20} defaultValue={[0, 100]} marks={healthSorceList} onAfterChange={onSilderChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-filter">
|
|
||||||
<div className="multi-cluster-filter-select">
|
|
||||||
<Select
|
|
||||||
onChange={(value) => onSelectChange('sortField', value)}
|
|
||||||
defaultValue="HealthScore"
|
|
||||||
style={{ width: 170, marginRight: 12 }}
|
|
||||||
>
|
|
||||||
{sortFieldList.map((item) => (
|
|
||||||
<Option key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
<Select onChange={(value) => onSelectChange('sortType', value)} defaultValue="asc" style={{ width: 170 }}>
|
|
||||||
{sortTypes.map((item) => (
|
|
||||||
<Option key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-filter-checkbox">
|
|
||||||
<CheckboxGroup options={statusFilters} value={statusList} onChange={onStatusChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="test-modal-23"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-page-dashboard">
|
|
||||||
<Spin spinning={clusterLoading}>{renderList}</Spin>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderList = useMemo(() => {
|
|
||||||
return <ListScroll list={list} pagination={pagination} loadMoreData={getPhyClustersDashbord} getPhyClusterState={getPhyClusterState} />;
|
|
||||||
}, [list, pagination]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TourGuide guide={MultiPageSteps} run={run} />
|
{pageLoading ? (
|
||||||
{pageLoading ? renderLoading() : stateInfo.total ? renderContent() : renderEmpty()}
|
<LoadingState />
|
||||||
|
) : !stateInfo?.total ? (
|
||||||
|
<DefaultPage setVisible={setAccessClusterVisible} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="multi-cluster-page" id="scrollableDiv">
|
||||||
|
<div className="multi-cluster-page-fixed">
|
||||||
|
<div className="content-container">
|
||||||
|
<div className="multi-cluster-header">
|
||||||
|
<div className="cluster-header-card">
|
||||||
|
<div className="cluster-header-card-bg-left"></div>
|
||||||
|
<div className="cluster-header-card-bg-right"></div>
|
||||||
|
<h5 className="header-card-title">
|
||||||
|
Clusters<span className="chinese-text"> 总数</span>
|
||||||
|
</h5>
|
||||||
|
<div className="header-card-total">{stateInfo.total}</div>
|
||||||
|
<div className="header-card-info">
|
||||||
|
<div className="card-info-item card-info-item-live">
|
||||||
|
<div>
|
||||||
|
live
|
||||||
|
<span className="info-item-value">
|
||||||
|
<em>{stateInfo.liveCount}</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-info-item card-info-item-down">
|
||||||
|
<div>
|
||||||
|
down
|
||||||
|
<span className="info-item-value">
|
||||||
|
<em>{stateInfo.downCount}</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="cluster-header-filter">
|
||||||
|
<div className="header-filter-top">
|
||||||
|
<div className="header-filter-top-input">
|
||||||
|
<Input
|
||||||
|
onPressEnter={searchParamsChangeFunc.onInputChange}
|
||||||
|
onChange={(e) => (searchKeyword.current = e.target.value)}
|
||||||
|
allowClear
|
||||||
|
bordered={false}
|
||||||
|
placeholder="请输入ClusterName进行搜索"
|
||||||
|
suffix={<IconFont className="icon" type="icon-fangdajing" onClick={searchParamsChangeFunc.onInputChange} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? (
|
||||||
|
<>
|
||||||
|
<div className="header-filter-top-divider"></div>
|
||||||
|
<Button className="header-filter-top-button" type="primary" onClick={() => showAccessCluster()}>
|
||||||
|
<IconFont type="icon-jiahao" />
|
||||||
|
<span className="text">接入集群</span>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="header-filter-bottom">
|
||||||
|
<div className="header-filter-bottom-item header-filter-bottom-item-checkbox">
|
||||||
|
<h3 className="header-filter-bottom-item-title">版本分布</h3>
|
||||||
|
<div className="header-filter-bottom-item-content flex">
|
||||||
|
{existKafkaVersion.length ? (
|
||||||
|
<CustomCheckGroup
|
||||||
|
kafkaVersions={existKafkaVersion}
|
||||||
|
onChangeCheckGroup={searchParamsChangeFunc.onChangeCheckGroup}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
||||||
|
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
||||||
|
<div className="header-filter-bottom-item-content">
|
||||||
|
<Slider
|
||||||
|
range
|
||||||
|
step={20}
|
||||||
|
defaultValue={[0, 100]}
|
||||||
|
marks={healthSorceList}
|
||||||
|
onAfterChange={searchParamsChangeFunc.onSilderChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-filter">
|
||||||
|
<div className="multi-cluster-filter-select">
|
||||||
|
<Select
|
||||||
|
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
|
||||||
|
defaultValue="HealthScore"
|
||||||
|
style={{ width: 170, marginRight: 12 }}
|
||||||
|
>
|
||||||
|
{sortFieldList.map((item) => (
|
||||||
|
<Option key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortType', value)}
|
||||||
|
defaultValue="asc"
|
||||||
|
style={{ width: 170 }}
|
||||||
|
>
|
||||||
|
{sortTypes.map((item) => (
|
||||||
|
<Option key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-filter-checkbox">
|
||||||
|
<CheckboxGroup
|
||||||
|
options={statusFilters}
|
||||||
|
value={searchParams.clusterStatus}
|
||||||
|
onChange={searchParamsChangeFunc.onClusterStatusChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-page-dashboard">
|
||||||
|
<ClusterList
|
||||||
|
searchParams={searchParams}
|
||||||
|
showAccessCluster={showAccessCluster}
|
||||||
|
getPhyClusterState={getPhyClusterState}
|
||||||
|
getExistKafkaVersion={getExistKafkaVersion}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 引导页 */}
|
||||||
|
<TourGuide guide={MultiPageSteps} run={true} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<AccessClusters
|
<AccessClusters
|
||||||
visible={visible}
|
clusterInfo={curClusterInfo}
|
||||||
setVisible={setVisible}
|
|
||||||
kafkaVersion={kafkaVersions}
|
kafkaVersion={kafkaVersions}
|
||||||
afterSubmitSuccess={afterSubmitSuccessAccessClusters}
|
visible={accessClusterVisible}
|
||||||
|
setVisible={setAccessClusterVisible}
|
||||||
|
afterSubmitSuccess={afterAccessCluster}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,38 +1,51 @@
|
|||||||
import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
|
import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useEffect, useMemo, useState, useReducer } from 'react';
|
import API from '@src/api';
|
||||||
|
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { timeFormat } from '../../constants/common';
|
import { timeFormat, oneDayMillims } from '@src/constants/common';
|
||||||
import { IMetricPoint, linesMetric } from './config';
|
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import api, { MetricType } from '../../api';
|
import api, { MetricType } from '@src/api';
|
||||||
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
|
import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
|
||||||
import SmallChart from '@src/components/SmallChart';
|
import SmallChart from '@src/components/SmallChart';
|
||||||
|
import { SearchParams } from './HomePage';
|
||||||
|
|
||||||
const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getPhyClusterState: any }) => {
|
const DEFAULT_PAGE_SIZE = 10;
|
||||||
const history = useHistory();
|
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const DeleteCluster = React.forwardRef((_, ref) => {
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [list, setList] = useState<[]>(props.list || []);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [clusterInfo, setClusterInfo] = useState({} as any);
|
|
||||||
const [pagination, setPagination] = useState(
|
|
||||||
props.pagination || {
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [visible, setVisible] = useState<boolean>(false);
|
||||||
|
const [clusterInfo, setClusterInfo] = useState<any>({});
|
||||||
|
const callback = useRef(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const onFinish = () => {
|
||||||
setList(props.list || []);
|
form.validateFields().then(() => {
|
||||||
setPagination(props.pagination || {});
|
Utils.delete(api.phyCluster, {
|
||||||
}, [props.list, props.pagination]);
|
params: {
|
||||||
|
clusterPhyId: clusterInfo.id,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
message.success('删除成功');
|
||||||
|
callback.current();
|
||||||
|
setVisible(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
onOpen: (clusterInfo: any, cbk: () => void) => {
|
||||||
|
setClusterInfo(clusterInfo);
|
||||||
|
callback.current = cbk;
|
||||||
|
setVisible(true);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -40,19 +53,164 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={570}
|
||||||
|
destroyOnClose={true}
|
||||||
|
centered={true}
|
||||||
|
className="custom-modal"
|
||||||
|
wrapClassName="del-topic-modal delete-modal"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.title',
|
||||||
|
})}
|
||||||
|
visible={visible}
|
||||||
|
onOk={onFinish}
|
||||||
|
okText={intl.formatMessage({
|
||||||
|
id: 'btn.delete',
|
||||||
|
})}
|
||||||
|
cancelText={intl.formatMessage({
|
||||||
|
id: 'btn.cancel',
|
||||||
|
})}
|
||||||
|
onCancel={() => setVisible(false)}
|
||||||
|
okButtonProps={{
|
||||||
|
style: {
|
||||||
|
width: 56,
|
||||||
|
},
|
||||||
|
danger: true,
|
||||||
|
size: 'small',
|
||||||
|
}}
|
||||||
|
cancelButtonProps={{
|
||||||
|
style: {
|
||||||
|
width: 56,
|
||||||
|
},
|
||||||
|
size: 'small',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="tip-info">
|
||||||
|
<IconFont type="icon-warning-circle"></IconFont>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.tip',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Form form={form} className="form" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} autoComplete="off">
|
||||||
|
<Form.Item label="集群名称" name="name">
|
||||||
|
<span>{clusterInfo.name}</span>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="集群名称"
|
||||||
|
name="clusterName"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.cluster',
|
||||||
|
}),
|
||||||
|
validator: (rule: any, value: string) => {
|
||||||
|
value = value || '';
|
||||||
|
if (!value.trim() || value.trim() !== clusterInfo.name)
|
||||||
|
return Promise.reject(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.cluster',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any; getPhyClusterState: any; getExistKafkaVersion: any }) => {
|
||||||
|
const { searchParams, showAccessCluster, getPhyClusterState, getExistKafkaVersion } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
const [global] = AppContainer.useGlobalValue();
|
||||||
|
const [isReload, setIsReload] = useState<boolean>(false);
|
||||||
|
const [list, setList] = useState<[]>([]);
|
||||||
|
const [clusterLoading, setClusterLoading] = useState<boolean>(true);
|
||||||
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const deleteModalRef = useRef(null);
|
||||||
|
|
||||||
|
const getClusterList = (pageNo: number, pageSize: number) => {
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
const startTime = endTime - oneDayMillims;
|
||||||
|
const params = {
|
||||||
|
metricLines: {
|
||||||
|
endTime,
|
||||||
|
metricsNames: linesMetric,
|
||||||
|
startTime,
|
||||||
|
},
|
||||||
|
latestMetricNames: pointsMetric,
|
||||||
|
pageNo: pageNo,
|
||||||
|
pageSize: pageSize,
|
||||||
|
preciseFilterDTOList: [
|
||||||
|
{
|
||||||
|
fieldName: 'kafkaVersion',
|
||||||
|
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rangeFilterDTOList: [
|
||||||
|
{
|
||||||
|
fieldMaxValue: searchParams.healthScoreRange[1],
|
||||||
|
fieldMinValue: searchParams.healthScoreRange[0],
|
||||||
|
fieldName: 'HealthScore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
searchKeywords: searchParams.keywords,
|
||||||
|
...searchParams.sortInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchParams.clusterStatus.length === 1) {
|
||||||
|
params.preciseFilterDTOList.push({
|
||||||
|
fieldName: 'Alive',
|
||||||
|
fieldValueList: searchParams.clusterStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Utils.post(API.phyClustersDashbord, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置集群列表
|
||||||
|
const reloadClusterList = (pageSize = DEFAULT_PAGE_SIZE) => {
|
||||||
|
setClusterLoading(true);
|
||||||
|
getClusterList(1, pageSize)
|
||||||
|
.then((res: any) => {
|
||||||
|
setList(res?.bizData || []);
|
||||||
|
setPagination(res.pagination);
|
||||||
|
})
|
||||||
|
.finally(() => setClusterLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多列表
|
||||||
const loadMoreData = async () => {
|
const loadMoreData = async () => {
|
||||||
if (loading) {
|
if (isLoadingMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setIsLoadingMore(true);
|
||||||
|
|
||||||
const res = await props.loadMoreData(pagination.pageNo + 1, pagination.pageSize);
|
const res: any = await getClusterList(pagination.pageNo + 1, pagination.pageSize);
|
||||||
const _data = list.concat(res.bizData || []) as any;
|
const _data = list.concat(res.bizData || []) as any;
|
||||||
setList(_data);
|
setList(_data);
|
||||||
setPagination(res.pagination);
|
setPagination(res.pagination);
|
||||||
setLoading(false);
|
setIsLoadingMore(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重载列表
|
||||||
|
useEffect(
|
||||||
|
() => (searchParams.isReloadAll ? reloadClusterList(pagination.pageNo * pagination.pageSize) : reloadClusterList()),
|
||||||
|
[searchParams]
|
||||||
|
);
|
||||||
|
|
||||||
const RenderItem = (itemData: any) => {
|
const RenderItem = (itemData: any) => {
|
||||||
itemData = itemData || {};
|
itemData = itemData || {};
|
||||||
const metrics = linesMetric;
|
const metrics = linesMetric;
|
||||||
@@ -160,7 +318,7 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
尚未开启 {name} 均衡策略,
|
尚未开启 {name} 均衡策略,
|
||||||
<Link to={`/cluster/${itemData.id}/cluster/balance`}>前往开启</Link>
|
<Link to={`/cluster/${itemData.id}/operation/balance`}>前往开启</Link>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -225,11 +383,34 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) ? (
|
{global.hasPermission ? (
|
||||||
<div className="multi-cluster-list-item-btn">
|
<div className="multi-cluster-list-item-btn">
|
||||||
<div className="icon" onClick={(event) => onClickDeleteBtn(event, itemData)}>
|
{global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) && (
|
||||||
<IconFont type="icon-shanchu1" />
|
<div
|
||||||
</div>
|
className="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showAccessCluster(itemData);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFont type="icon-duojiqunbianji" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) && (
|
||||||
|
<div
|
||||||
|
className="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteModalRef.current.onOpen(itemData, () => {
|
||||||
|
getPhyClusterState();
|
||||||
|
getExistKafkaVersion(true);
|
||||||
|
reloadClusterList(pagination.pageNo * pagination.pageSize);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFont type="icon-duojiqunshanchu" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -239,45 +420,21 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFinish = () => {
|
|
||||||
form.validateFields().then((formData) => {
|
|
||||||
Utils.delete(api.phyCluster, {
|
|
||||||
params: {
|
|
||||||
clusterPhyId: clusterInfo.id,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
|
||||||
message.success('删除成功');
|
|
||||||
setVisible(false);
|
|
||||||
props?.getPhyClusterState();
|
|
||||||
const fliterList: any = list.filter((item: any) => {
|
|
||||||
return item?.id !== clusterInfo.id;
|
|
||||||
});
|
|
||||||
setList(fliterList || []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickDeleteBtn = (event: any, clusterInfo: any) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
setClusterInfo(clusterInfo);
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Spin spinning={clusterLoading}>
|
||||||
{useMemo(
|
{useMemo(
|
||||||
() => (
|
() => (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={list.length}
|
dataLength={list.length}
|
||||||
next={loadMoreData}
|
next={loadMoreData}
|
||||||
hasMore={list.length < pagination.total}
|
hasMore={list.length < pagination.total}
|
||||||
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={loading} />}
|
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={true} />}
|
||||||
endMessage={
|
endMessage={
|
||||||
!pagination.total ? (
|
!pagination.total ? (
|
||||||
''
|
''
|
||||||
) : (
|
) : (
|
||||||
<Divider className="load-completed-tip" plain>
|
<Divider className="load-completed-tip" plain>
|
||||||
加载完成 共{pagination.total}条
|
加载完成 共 {pagination.total} 条
|
||||||
</Divider>
|
</Divider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -293,81 +450,11 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
/>
|
/>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
),
|
),
|
||||||
[list, pagination, loading]
|
[list, pagination, isLoadingMore]
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<DeleteCluster ref={deleteModalRef} />
|
||||||
width={570}
|
</Spin>
|
||||||
destroyOnClose={true}
|
|
||||||
centered={true}
|
|
||||||
className="custom-modal"
|
|
||||||
wrapClassName="del-topic-modal delete-modal"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.title',
|
|
||||||
})}
|
|
||||||
visible={visible}
|
|
||||||
onOk={onFinish}
|
|
||||||
okText={intl.formatMessage({
|
|
||||||
id: 'btn.delete',
|
|
||||||
})}
|
|
||||||
cancelText={intl.formatMessage({
|
|
||||||
id: 'btn.cancel',
|
|
||||||
})}
|
|
||||||
onCancel={() => setVisible(false)}
|
|
||||||
okButtonProps={{
|
|
||||||
style: {
|
|
||||||
width: 56,
|
|
||||||
},
|
|
||||||
danger: true,
|
|
||||||
size: 'small',
|
|
||||||
}}
|
|
||||||
cancelButtonProps={{
|
|
||||||
style: {
|
|
||||||
width: 56,
|
|
||||||
},
|
|
||||||
size: 'small',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="tip-info">
|
|
||||||
<IconFont type="icon-warning-circle"></IconFont>
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.tip',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Form form={form} className="form" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} autoComplete="off">
|
|
||||||
<Form.Item label="集群名称" name="name" rules={[{ required: false, message: '' }]}>
|
|
||||||
<span>{clusterInfo.name}</span>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="集群名称"
|
|
||||||
name="clusterName"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.cluster',
|
|
||||||
}),
|
|
||||||
validator: (rule: any, value: string) => {
|
|
||||||
value = value || '';
|
|
||||||
if (!value.trim() || value.trim() !== clusterInfo.name)
|
|
||||||
return Promise.reject(
|
|
||||||
intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.cluster',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default ClusterList;
|
||||||
export default ListScroll;
|
|
||||||
|
|||||||
@@ -364,8 +364,12 @@
|
|||||||
.multi-cluster-list-item-btn {
|
.multi-cluster-list-item-btn {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
.icon {
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
background: rgba(33, 37, 41, 0.04);
|
||||||
|
border-radius: 12px;
|
||||||
color: #74788d;
|
color: #74788d;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:hover {
|
.icon:hover {
|
||||||
@@ -375,16 +379,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.multi-cluster-list-item-btn {
|
.multi-cluster-list-item-btn {
|
||||||
|
display: flex;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: rgba(33, 37, 41, 0.04);
|
|
||||||
border-radius: 14px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import moment from 'moment';
|
|||||||
import { timeFormat } from '../../constants/common';
|
import { timeFormat } from '../../constants/common';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import { renderToolTipValue } from './config';
|
import { renderToolTipValue } from './config';
|
||||||
|
import RenderEmpty from '@src/components/RenderEmpty';
|
||||||
|
|
||||||
const { Panel } = Collapse;
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
@@ -51,17 +52,6 @@ const ChangeLog = () => {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderEmpty = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="empty-panel">
|
|
||||||
<div className="img" />
|
|
||||||
<div className="text">暂无配置记录</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getHref = (item: any) => {
|
const getHref = (item: any) => {
|
||||||
if (item.resTypeName.toLowerCase().includes('topic')) return `/cluster/${clusterId}/topic/list#topicName=${item.resName}`;
|
if (item.resTypeName.toLowerCase().includes('topic')) return `/cluster/${clusterId}/topic/list#topicName=${item.resName}`;
|
||||||
if (item.resTypeName.toLowerCase().includes('broker')) return `/cluster/${clusterId}/broker/list#brokerId=${item.resName}`;
|
if (item.resTypeName.toLowerCase().includes('broker')) return `/cluster/${clusterId}/broker/list#brokerId=${item.resName}`;
|
||||||
@@ -73,7 +63,7 @@ const ChangeLog = () => {
|
|||||||
<div className="change-log-panel">
|
<div className="change-log-panel">
|
||||||
<div className="title">历史变更记录</div>
|
<div className="title">历史变更记录</div>
|
||||||
{!loading && !data.length ? (
|
{!loading && !data.length ? (
|
||||||
renderEmpty()
|
<RenderEmpty message="暂无配置记录" />
|
||||||
) : (
|
) : (
|
||||||
<div id="changelog-scroll-box">
|
<div id="changelog-scroll-box">
|
||||||
<Spin spinning={loading} style={{ paddingLeft: '42%', marginTop: 100 }} />
|
<Spin spinning={loading} style={{ paddingLeft: '42%', marginTop: 100 }} />
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { Drawer, Form, Spin, Table, Utils } from 'knowdesign';
|
import { Drawer, Spin, Table, Utils } from 'knowdesign';
|
||||||
import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
|
import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { getDetailColumn } from './config';
|
import { getDetailColumn } from './config';
|
||||||
import API from '../../api';
|
import API from '../../api';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
const CheckDetail = forwardRef((props: any, ref): JSX.Element => {
|
const CheckDetail = forwardRef((props: any, ref): JSX.Element => {
|
||||||
const intl = useIntl();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
@@ -28,7 +25,6 @@ const CheckDetail = forwardRef((props: any, ref): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
form.resetFields();
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,15 +38,17 @@
|
|||||||
|
|
||||||
&-main {
|
&-main {
|
||||||
.header-chart-container {
|
.header-chart-container {
|
||||||
&-loading {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 244px;
|
height: 244px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
.cluster-container-border();
|
.cluster-container-border();
|
||||||
|
.dcloud-spin.dcloud-spin-spinning {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 244px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { MetricType } from '@src/api';
|
|||||||
import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
|
import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
|
||||||
import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
|
import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
|
||||||
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
|
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
|
||||||
|
import RenderEmpty from '@src/components/RenderEmpty';
|
||||||
|
|
||||||
type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>;
|
type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>;
|
||||||
interface MetricInfo {
|
interface MetricInfo {
|
||||||
@@ -64,7 +65,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
|||||||
const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({
|
const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({
|
||||||
name: 'MessagesIn',
|
name: 'MessagesIn',
|
||||||
unit: '',
|
unit: '',
|
||||||
data: [],
|
data: undefined,
|
||||||
});
|
});
|
||||||
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
|
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
|
||||||
const [defaultChartLoading, setDefaultChartLoading] = useState<boolean>(true);
|
const [defaultChartLoading, setDefaultChartLoading] = useState<boolean>(true);
|
||||||
@@ -234,17 +235,19 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
|||||||
result.forEach((point) => ((point[1] as number) /= unitSize));
|
result.forEach((point) => ((point[1] as number) /= unitSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 补充缺少的图表点
|
if (result.length) {
|
||||||
const extraMetrics = result[0][2].map((info) => ({
|
// 补充缺少的图表点
|
||||||
...info,
|
const extraMetrics = result[0][2].map((info) => ({
|
||||||
value: 0,
|
...info,
|
||||||
}));
|
value: 0,
|
||||||
const supplementaryInterval =
|
}));
|
||||||
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
|
const supplementaryInterval =
|
||||||
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
|
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
|
||||||
point.push(extraMetrics as any);
|
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
|
||||||
return point;
|
point.push(extraMetrics as any);
|
||||||
});
|
return point;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setMessagesInMetricData(line);
|
setMessagesInMetricData(line);
|
||||||
setDefaultChartLoading(false);
|
setDefaultChartLoading(false);
|
||||||
@@ -299,10 +302,9 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
|||||||
|
|
||||||
<div className="cluster-detail-container-main">
|
<div className="cluster-detail-container-main">
|
||||||
{/* MessageIn 图表 */}
|
{/* MessageIn 图表 */}
|
||||||
<div className={`header-chart-container ${!messagesInMetricData.data.length ? 'header-chart-container-loading' : ''}`}>
|
<div className="header-chart-container">
|
||||||
<Spin spinning={defaultChartLoading}>
|
<Spin spinning={defaultChartLoading}>
|
||||||
{/* TODO: 暂时通过判断是否有图表数据来修复,有时间可以查找下宽度溢出的原因 */}
|
{messagesInMetricData.data && (
|
||||||
{messagesInMetricData.data.length ? (
|
|
||||||
<>
|
<>
|
||||||
<div className="chart-box-title">
|
<div className="chart-box-title">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -322,26 +324,27 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<SingleChart
|
{messagesInMetricData.data.length ? (
|
||||||
chartKey="messagesIn"
|
<SingleChart
|
||||||
chartTypeProp="line"
|
chartKey="messagesIn"
|
||||||
showHeader={false}
|
chartTypeProp="line"
|
||||||
wrapStyle={{
|
showHeader={false}
|
||||||
width: 'auto',
|
wrapStyle={{
|
||||||
height: 210,
|
width: 'auto',
|
||||||
}}
|
height: 210,
|
||||||
connectEventName="clusterChart"
|
}}
|
||||||
eventBus={busInstance}
|
connectEventName="clusterChart"
|
||||||
propChartData={[messagesInMetricData]}
|
eventBus={busInstance}
|
||||||
{...getChartConfig({
|
propChartData={[messagesInMetricData]}
|
||||||
// metricName: `${messagesInMetricData.name}{unit|(${messagesInMetricData.unit})}`,
|
{...getChartConfig({
|
||||||
lineColor: CHART_LINE_COLORS[0],
|
lineColor: CHART_LINE_COLORS[0],
|
||||||
isDefaultMetric: true,
|
isDefaultMetric: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
!defaultChartLoading && <RenderEmpty message="暂无数据" height={200} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
@@ -408,7 +411,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
|||||||
) : chartLoading ? (
|
) : chartLoading ? (
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="请先选择指标或刷新" style={{ width: '100%', height: '100%' }} />
|
<RenderEmpty message="请先选择指标或刷新" />
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ const LeftSider = () => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<div className="name">{renderToolTipValue(clusterInfo?.name, 35)}</div>
|
<div className="name">{renderToolTipValue(clusterInfo?.name, 35)}</div>
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) ? (
|
{!loading && global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) ? (
|
||||||
<div className="edit-icon-box" onClick={() => setVisible(true)}>
|
<div className="edit-icon-box" onClick={() => setVisible(true)}>
|
||||||
<IconFont className="edit-icon" type="icon-bianji2" />
|
<IconFont className="edit-icon" type="icon-bianji2" />
|
||||||
</div>
|
</div>
|
||||||
@@ -239,8 +239,7 @@ const LeftSider = () => {
|
|||||||
<AccessClusters
|
<AccessClusters
|
||||||
visible={visible}
|
visible={visible}
|
||||||
setVisible={setVisible}
|
setVisible={setVisible}
|
||||||
title={'edit.cluster'}
|
title="edit.cluster"
|
||||||
infoLoading={loading}
|
|
||||||
afterSubmitSuccess={getPhyClusterInfo}
|
afterSubmitSuccess={getPhyClusterInfo}
|
||||||
clusterInfo={clusterInfo}
|
clusterInfo={clusterInfo}
|
||||||
kafkaVersion={Object.keys(kafkaVersion)}
|
kafkaVersion={Object.keys(kafkaVersion)}
|
||||||
|
|||||||
@@ -267,10 +267,10 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
|
|||||||
<InputNumber
|
<InputNumber
|
||||||
size="small"
|
size="small"
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={1}
|
||||||
style={{ width: 86 }}
|
style={{ width: 86 }}
|
||||||
formatter={(value) => `${value}%`}
|
formatter={(value) => `${value * 100}%`}
|
||||||
parser={(value: any) => value.replace('%', '')}
|
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
||||||
|
|||||||
@@ -377,26 +377,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-panel {
|
|
||||||
margin-top: 96px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.img {
|
|
||||||
width: 51px;
|
|
||||||
height: 34px;
|
|
||||||
margin-left: 80px;
|
|
||||||
margin-bottom: 7px;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url('../../assets/empty.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #919aac;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,38 +178,35 @@ const ConsumeClientTest = () => {
|
|||||||
partitionProcessRef.current = processList;
|
partitionProcessRef.current = processList;
|
||||||
|
|
||||||
curPartitionList.current = _partitionList;
|
curPartitionList.current = _partitionList;
|
||||||
|
if (!isStopStatus.current) {
|
||||||
switch (until) {
|
switch (until) {
|
||||||
case 'timestamp':
|
case 'timestamp':
|
||||||
setIsStop(currentTime >= untilDate);
|
setIsStop(currentTime >= untilDate);
|
||||||
isStopStatus.current = currentTime >= untilDate;
|
isStopStatus.current = currentTime >= untilDate;
|
||||||
break;
|
break;
|
||||||
case 'number of messages':
|
case 'number of messages':
|
||||||
setIsStop(+recordCountCur.current >= untilMsgNum);
|
setIsStop(+recordCountCur.current >= untilMsgNum);
|
||||||
isStopStatus.current = +recordCountCur.current >= untilMsgNum;
|
isStopStatus.current = +recordCountCur.current >= untilMsgNum;
|
||||||
break;
|
break;
|
||||||
case 'number of messages per partition': // 所有分区都达到了设定值
|
case 'number of messages per partition': // 所有分区都达到了设定值
|
||||||
// 过滤出消费数量不足设定值的partition
|
// 过滤出消费数量不足设定值的partition
|
||||||
const filtersPartition = _partitionList.filter((item: any) => item.recordCount < untilMsgNum);
|
const filtersPartition = _partitionList.filter((item: any) => item.recordCount < untilMsgNum);
|
||||||
curPartitionList.current = filtersPartition; // 用作下一次请求的入参
|
curPartitionList.current = filtersPartition; // 用作下一次请求的入参
|
||||||
if (!isStop) {
|
|
||||||
setIsStop(filtersPartition.length < 1);
|
setIsStop(filtersPartition.length < 1);
|
||||||
isStopStatus.current = filtersPartition.length < 1;
|
isStopStatus.current = filtersPartition.length < 1;
|
||||||
}
|
break;
|
||||||
break;
|
case 'max size':
|
||||||
case 'max size':
|
setIsStop(+recordSizeCur.current >= unitMsgSize);
|
||||||
setIsStop(+recordSizeCur.current >= unitMsgSize);
|
isStopStatus.current = +recordSizeCur.current >= unitMsgSize;
|
||||||
isStopStatus.current = +recordSizeCur.current >= unitMsgSize;
|
break;
|
||||||
break;
|
case 'max size per partition':
|
||||||
case 'max size per partition':
|
// 过滤出消费size不足设定值的partition
|
||||||
// 过滤出消费size不足设定值的partition
|
const filters = partitionConsumedList.filter((item: any) => item.recordSizeUnitB < unitMsgSize);
|
||||||
const filters = partitionConsumedList.filter((item: any) => item.recordSizeUnitB < unitMsgSize);
|
|
||||||
if (!isStop) {
|
|
||||||
setIsStop(filters.length < 1);
|
setIsStop(filters.length < 1);
|
||||||
isStopStatus.current = filters.length < 1;
|
isStopStatus.current = filters.length < 1;
|
||||||
}
|
curPartitionList.current = filters;
|
||||||
curPartitionList.current = filters;
|
break;
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Table, Input, InputNumber, Popconfirm, Form, Typography, Button, message, IconFont } from 'knowdesign';
|
import { Table, Input, InputNumber, Popconfirm, Form, Typography, Button, message, IconFont, Select } from 'knowdesign';
|
||||||
import './style/edit-table.less';
|
import './style/edit-table.less';
|
||||||
import { CheckOutlined, CloseOutlined, PlusSquareOutlined } from '@ant-design/icons';
|
import { CheckOutlined, CloseOutlined, PlusSquareOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const EditableCell = ({ editing, dataIndex, title, inputType, placeholder, record, index, children, ...restProps }: any) => {
|
const EditableCell = ({ editing, dataIndex, title, inputType, placeholder, record, index, children, options, ...restProps }: any) => {
|
||||||
const inputNode =
|
const inputNode =
|
||||||
inputType === 'number' ? (
|
inputType === 'number' ? (
|
||||||
<InputNumber style={{ width: '130px' }} autoComplete="off" placeholder={placeholder} />
|
<InputNumber min={0} precision={0} style={{ width: '130px' }} autoComplete="off" placeholder={placeholder} />
|
||||||
|
) : inputType === 'select' ? (
|
||||||
|
<Select style={{ width: '140px' }} options={options || []} placeholder={placeholder} />
|
||||||
) : (
|
) : (
|
||||||
<Input autoComplete="off" placeholder={placeholder} />
|
<Input autoComplete="off" placeholder={placeholder} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import api, { MetricType } from '@src/api';
|
|||||||
import { useParams } from 'react-router-dom';
|
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';
|
||||||
|
|
||||||
interface PropsType {
|
interface PropsType {
|
||||||
hashData: any;
|
hashData: any;
|
||||||
@@ -86,18 +87,6 @@ function getTranformedBytes(bytes: number) {
|
|||||||
return [outBytes.toFixed(2), unit[i]];
|
return [outBytes.toFixed(2), unit[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderEmpty = (props: { message: string }) => {
|
|
||||||
const { message } = props;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="empty-panel">
|
|
||||||
<div className="img" />
|
|
||||||
<div className="text">{message}</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PartitionPopoverContent = (props: {
|
const PartitionPopoverContent = (props: {
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
hashData: any;
|
hashData: any;
|
||||||
@@ -125,18 +114,21 @@ const PartitionPopoverContent = (props: {
|
|||||||
{ label: 'LeaderBroker', value: leaderBrokerId },
|
{ label: 'LeaderBroker', value: leaderBrokerId },
|
||||||
{
|
{
|
||||||
label: 'BeginningOffset',
|
label: 'BeginningOffset',
|
||||||
value: `${metricsData.LogStartOffset === undefined ? '-' : metricsData.LogStartOffset} ${global.getMetricDefine(type, 'LogStartOffset')?.unit || ''
|
value: `${metricsData.LogStartOffset === undefined ? '-' : metricsData.LogStartOffset} ${
|
||||||
}`,
|
global.getMetricDefine(type, 'LogStartOffset')?.unit || ''
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'EndOffset',
|
label: 'EndOffset',
|
||||||
value: `${metricsData.LogEndOffset === undefined ? '-' : metricsData.LogEndOffset} ${global.getMetricDefine(type, 'LogEndOffset')?.unit || ''
|
value: `${metricsData.LogEndOffset === undefined ? '-' : metricsData.LogEndOffset} ${
|
||||||
}`,
|
global.getMetricDefine(type, 'LogEndOffset')?.unit || ''
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'MsgNum',
|
label: 'MsgNum',
|
||||||
value: `${metricsData.Messages === undefined ? '-' : metricsData.Messages} ${global.getMetricDefine(type, 'Messages')?.unit || ''
|
value: `${metricsData.Messages === undefined ? '-' : metricsData.Messages} ${
|
||||||
}`,
|
global.getMetricDefine(type, 'Messages')?.unit || ''
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'LogSize',
|
label: 'LogSize',
|
||||||
@@ -281,13 +273,14 @@ const PartitionCard = (props: { clusterId: string; hashData: any }) => {
|
|||||||
<div className="broker-container-box-detail">
|
<div className="broker-container-box-detail">
|
||||||
{partitionState.alive ? (
|
{partitionState.alive ? (
|
||||||
partitionState?.replicaList?.length ? (
|
partitionState?.replicaList?.length ? (
|
||||||
<div className="partition-list">
|
<div className={`partition-list ${hoverPartitionId !== -1 ? 'partition-list-hover-state' : ''}`}>
|
||||||
{partitionState?.replicaList?.map((partition) => {
|
{partitionState?.replicaList?.map((partition) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={partition.partitionId}
|
key={partition.partitionId}
|
||||||
className={`partition-list-item partition-list-item-${partition.isLeaderReplace ? 'leader' : partition.inSync ? 'isr' : 'osr'
|
className={`partition-list-item partition-list-item-${
|
||||||
} ${partition.partitionId === hoverPartitionId ? 'partition-active' : ''}`}
|
partition.isLeaderReplace ? 'leader' : partition.inSync ? 'isr' : 'osr'
|
||||||
|
} ${partition.partitionId === hoverPartitionId ? 'partition-active' : ''}`}
|
||||||
onMouseEnter={() => setHoverPartitionId(partition.partitionId)}
|
onMouseEnter={() => setHoverPartitionId(partition.partitionId)}
|
||||||
onMouseLeave={() => setHoverPartitionId(-1)}
|
onMouseLeave={() => setHoverPartitionId(-1)}
|
||||||
onClick={() => setClickPartition(`${partitionState.brokerId}&${partition.partitionId}`)}
|
onClick={() => setClickPartition(`${partitionState.brokerId}&${partition.partitionId}`)}
|
||||||
@@ -316,10 +309,10 @@ const PartitionCard = (props: { clusterId: string; hashData: any }) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<RenderEmpty message="暂无数据" />
|
<RenderEmpty message="暂无数据" height="unset" />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<RenderEmpty message="暂无数据" />
|
<RenderEmpty message="暂无数据" height="unset" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
props.setVisible(false);
|
props.setVisible(false);
|
||||||
props.genData({ pageNo: 1, pageSize: 10 });
|
props.genData({ pageNo: 1, pageSize: 10 });
|
||||||
})
|
})
|
||||||
.catch((err: any) => { });
|
.catch((err: any) => {});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
form.setFieldsValue(props.record);
|
||||||
|
}, [props.record]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
title={
|
title={
|
||||||
@@ -43,6 +47,7 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
visible={props.visible}
|
visible={props.visible}
|
||||||
onClose={() => props.setVisible(false)}
|
onClose={() => props.setVisible(false)}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
destroyOnClose
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button size="small" onClick={onClose}>
|
<Button size="small" onClick={onClose}>
|
||||||
@@ -76,7 +81,7 @@ export const ConfigurationEdit = (props: any) => {
|
|||||||
{props.record?.documentation || '-'}
|
{props.record?.documentation || '-'}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Form form={form} layout={'vertical'} initialValues={props.record}>
|
<Form form={form} layout={'vertical'}>
|
||||||
<Form.Item name="defaultValue" label="Kafka默认配置">
|
<Form.Item name="defaultValue" label="Kafka默认配置">
|
||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const getTopicMessagesColmns = () => {
|
|||||||
key: 'partitionId',
|
key: 'partitionId',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'offset',
|
title: 'Offset',
|
||||||
dataIndex: 'offset',
|
dataIndex: 'offset',
|
||||||
key: 'offset',
|
key: 'offset',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -215,8 +215,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 324px;
|
width: 324px;
|
||||||
min-height: calc(100% - 66px);
|
min-height: calc(100% - 66px);
|
||||||
margin: 0 0 12px 6px;
|
margin: 0 6px 6px 6px;
|
||||||
padding: 22px 20px 0 20px;
|
padding: 12px 12px 0 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -226,16 +226,16 @@
|
|||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
&-item {
|
&-item {
|
||||||
width: 32px;
|
width: 34px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-bottom: 22px;
|
margin-bottom: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
transition: all ease 0.2s;
|
transition: all ease-in-out 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:not(&:nth-of-type(5n)) {
|
&:not(&:nth-of-type(8n)) {
|
||||||
margin-right: 31px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
&-leader {
|
&-leader {
|
||||||
background: rgba(85, 110, 230, 0.1);
|
background: rgba(85, 110, 230, 0.1);
|
||||||
@@ -262,27 +262,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
&-hover-state {
|
||||||
|
.partition-list-item {
|
||||||
.empty-panel {
|
&-leader:not(.partition-active) {
|
||||||
display: flex;
|
background-color: #f6f7fd;
|
||||||
flex-direction: column;
|
color: #dbe1f8;
|
||||||
align-items: center;
|
}
|
||||||
margin-bottom: 18px;
|
&-isr:not(.partition-active) {
|
||||||
text-align: center;
|
background-color: #fcfcfc;
|
||||||
|
color: #c4c6c9;
|
||||||
.img {
|
}
|
||||||
width: 51px;
|
&-osr:not(.partition-active) {
|
||||||
height: 34px;
|
background-color: #fefaf4;
|
||||||
margin-bottom: 7px;
|
color: #f8d6af;
|
||||||
background-size: cover;
|
}
|
||||||
background-image: url('../../assets/empty.png');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #919aac;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import {
|
import { Alert, Button, Checkbox, Divider, Drawer, Form, Input, InputNumber, Modal, notification, Select, Utils } from 'knowdesign';
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Divider,
|
|
||||||
Drawer,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Modal,
|
|
||||||
notification,
|
|
||||||
Select,
|
|
||||||
Utils,
|
|
||||||
} from 'knowdesign';
|
|
||||||
import { PlusOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
|
import { PlusOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||||
import Api from '@src/api/index';
|
import Api from '@src/api/index';
|
||||||
|
|
||||||
@@ -120,9 +107,9 @@ export default (props: any) => {
|
|||||||
res =
|
res =
|
||||||
item.name === 'cleanup.policy'
|
item.name === 'cleanup.policy'
|
||||||
? item.defaultValue
|
? item.defaultValue
|
||||||
.replace(/\[|\]|\s+/g, '')
|
.replace(/\[|\]|\s+/g, '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter((_) => _)
|
.filter((_) => _)
|
||||||
: item.defaultValue;
|
: item.defaultValue;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res = [];
|
res = [];
|
||||||
@@ -317,7 +304,7 @@ export default (props: any) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="create-topic-flex-layout">
|
<div className="create-topic-flex-layout">
|
||||||
<Form.Item name={['properties', 'max.message.bytes']} label="max message size">
|
<Form.Item name={['properties', 'max.message.bytes']} label="Max message size">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={0}
|
min={0}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@@ -329,7 +316,11 @@ export default (props: any) => {
|
|||||||
{defaultConfigs
|
{defaultConfigs
|
||||||
.filter((dc) => !customDefaultFields.includes(dc.name))
|
.filter((dc) => !customDefaultFields.includes(dc.name))
|
||||||
.map((configItem, i) => (
|
.map((configItem, i) => (
|
||||||
<Form.Item key={i} name={['properties', configItem.name]} label={configItem.name}>
|
<Form.Item
|
||||||
|
key={i}
|
||||||
|
name={['properties', configItem.name]}
|
||||||
|
label={configItem.name.slice(0, 1).toUpperCase() + configItem.name.slice(1)}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
> span {
|
> span {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
color: #74788d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
.batch-btn{
|
.batch-btn {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
.add-btn {
|
.add-btn {
|
||||||
@@ -51,44 +52,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.metric-data-wrap {
|
.metric-data-wrap {
|
||||||
display: flex;
|
// display: flex;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.cur-val {
|
.cur-val {
|
||||||
width: 34px;
|
display: block;
|
||||||
margin-right: 11px;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.dcloud-spin-nested-loading{
|
.dcloud-spin-nested-loading {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.del-topic-modal,
|
.del-topic-modal,
|
||||||
.cluster-topic-add {
|
.cluster-topic-add {
|
||||||
.tip-info {
|
.tip-info {
|
||||||
height: 27px;
|
display: flex;
|
||||||
line-height: 27px;
|
|
||||||
color: #592d00;
|
color: #592d00;
|
||||||
padding: 0 14px;
|
padding: 6px 14px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background: #fffae0;
|
background: #fffae0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
.anticon {
|
.anticon {
|
||||||
color: #ffc300;
|
color: #ffc300;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
.test-right-away {
|
.test-right-away {
|
||||||
color: #556ee6;
|
color: #556ee6;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.dcloud-alert-content{
|
.dcloud-alert-content {
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.cluster-topic-add {
|
.cluster-topic-add {
|
||||||
.data-save-time-label{
|
.data-save-time-label {
|
||||||
&>.dcloud-form-item-control{
|
& > .dcloud-form-item-control {
|
||||||
&>.dcloud-form-item-explain{
|
& > .dcloud-form-item-explain {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,11 +125,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #556EE6;
|
color: #556ee6;
|
||||||
.txt {
|
.txt {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
color: #556EE6;
|
color: #556ee6;
|
||||||
font-family: @font-family;
|
font-family: @font-family;
|
||||||
}
|
}
|
||||||
.anticon {
|
.anticon {
|
||||||
@@ -226,12 +227,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-topic-flex-layout {
|
||||||
.create-topic-flex-layout{
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
.dcloud-form-item{
|
.dcloud-form-item {
|
||||||
width: 370px;
|
width: 370px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,21 +91,9 @@ const AutoPage = (props: any) => {
|
|||||||
if (metricName === 'HealthScore') {
|
if (metricName === 'HealthScore') {
|
||||||
return Math.round(orgVal);
|
return Math.round(orgVal);
|
||||||
} else if (metricName === 'LogSize') {
|
} else if (metricName === 'LogSize') {
|
||||||
return Number(Utils.formatAssignSize(orgVal, 'MB')).toString().length > 3 ? (
|
return Number(Utils.formatAssignSize(orgVal, 'MB'));
|
||||||
<Tooltip title={Utils.formatAssignSize(orgVal, 'MB')}>
|
|
||||||
{Number(Utils.formatAssignSize(orgVal, 'MB')).toString().slice(0, 3) + '...'}
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
Number(Utils.formatAssignSize(orgVal, 'MB'))
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Number(Utils.formatAssignSize(orgVal, 'KB')).toString().length > 3 ? (
|
return Number(Utils.formatAssignSize(orgVal, 'KB'));
|
||||||
<Tooltip title={Utils.formatAssignSize(orgVal, 'KB')}>
|
|
||||||
{Number(Utils.formatAssignSize(orgVal, 'KB')).toString().slice(0, 3) + '...'}
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
Number(Utils.formatAssignSize(orgVal, 'KB'))
|
|
||||||
);
|
|
||||||
// return Utils.formatAssignSize(orgVal, 'KB');
|
// return Utils.formatAssignSize(orgVal, 'KB');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,15 +104,15 @@ const AutoPage = (props: any) => {
|
|||||||
const points = record.metricLines.find((item: any) => item.metricName === metricName)?.metricPoints || [];
|
const points = record.metricLines.find((item: any) => item.metricName === metricName)?.metricPoints || [];
|
||||||
return (
|
return (
|
||||||
<div className="metric-data-wrap">
|
<div className="metric-data-wrap">
|
||||||
<span className="cur-val">{calcCurValue(record, metricName)}</span>
|
|
||||||
<SmallChart
|
<SmallChart
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
height={40}
|
height={30}
|
||||||
chartData={{
|
chartData={{
|
||||||
name: record.metricName,
|
name: record.metricName,
|
||||||
data: points.map((item: any) => ({ time: item.timeStamp, value: item.value })),
|
data: points.map((item: any) => ({ time: item.timeStamp, value: item.value })),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<span className="cur-val">{calcCurValue(record, metricName)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -268,12 +256,16 @@ const AutoPage = (props: any) => {
|
|||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item>
|
{global.hasPermission(ClustersPermissionMap.TOPIC_CHANGE_REPLICA) && (
|
||||||
<a onClick={() => setChangeVisible(true)}>扩缩副本</a>
|
<Menu.Item>
|
||||||
</Menu.Item>
|
<a onClick={() => setChangeVisible(true)}>扩缩副本</a>
|
||||||
<Menu.Item>
|
</Menu.Item>
|
||||||
<a onClick={() => setMoveVisible(true)}>迁移副本</a>
|
)}
|
||||||
</Menu.Item>
|
{global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA) && (
|
||||||
|
<Menu.Item>
|
||||||
|
<a onClick={() => setMoveVisible(true)}>迁移副本</a>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -345,11 +337,14 @@ const AutoPage = (props: any) => {
|
|||||||
setSearchKeywordsInput(e.target.value);
|
setSearchKeywordsInput(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
{(global.hasPermission(ClustersPermissionMap.TOPIC_CHANGE_REPLICA) ||
|
||||||
<Button className="batch-btn" icon={<DownOutlined />} type="primary" ghost>
|
global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA)) && (
|
||||||
批量操作
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
</Button>
|
<Button className="batch-btn" icon={<DownOutlined />} type="primary" ghost>
|
||||||
</Dropdown>
|
批量变更
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
)}
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.TOPIC_ADD) ? (
|
{global.hasPermission && global.hasPermission(ClustersPermissionMap.TOPIC_ADD) ? (
|
||||||
<Create onConfirm={getTopicsList}></Create>
|
<Create onConfirm={getTopicsList}></Create>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import HomePage from './MutliClusterPage/HomePage';
|
import ClusterManage from './MutliClusterPage/HomePage';
|
||||||
|
|
||||||
import { NoMatch } from '.';
|
import { NoMatch } from '.';
|
||||||
import CommonRoute from './CommonRoute';
|
import CommonRoute from './CommonRoute';
|
||||||
@@ -26,7 +26,7 @@ const pageRoutes = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
exact: true,
|
exact: true,
|
||||||
component: HomePage,
|
component: ClusterManage,
|
||||||
commonRoute: CommonConfig,
|
commonRoute: CommonConfig,
|
||||||
noSider: true,
|
noSider: true,
|
||||||
},
|
},
|
||||||
@@ -37,15 +37,6 @@ const pageRoutes = [
|
|||||||
commonRoute: CommonRoute,
|
commonRoute: CommonRoute,
|
||||||
noSider: false,
|
noSider: false,
|
||||||
children: [
|
children: [
|
||||||
// 负载均衡
|
|
||||||
process.env.BUSINESS_VERSION
|
|
||||||
? {
|
|
||||||
path: 'cluster/balance',
|
|
||||||
exact: true,
|
|
||||||
component: LoadRebalance,
|
|
||||||
noSider: false,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
{
|
{
|
||||||
path: 'cluster',
|
path: 'cluster',
|
||||||
exact: true,
|
exact: true,
|
||||||
@@ -109,6 +100,21 @@ const pageRoutes = [
|
|||||||
component: Consumers,
|
component: Consumers,
|
||||||
noSider: false,
|
noSider: false,
|
||||||
},
|
},
|
||||||
|
// 负载均衡
|
||||||
|
process.env.BUSINESS_VERSION
|
||||||
|
? {
|
||||||
|
path: 'operation/balance',
|
||||||
|
exact: true,
|
||||||
|
component: LoadRebalance,
|
||||||
|
noSider: false,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
{
|
||||||
|
path: 'operation/jobs',
|
||||||
|
exact: true,
|
||||||
|
component: Jobs,
|
||||||
|
noSider: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'security/acls',
|
path: 'security/acls',
|
||||||
exact: true,
|
exact: true,
|
||||||
@@ -121,12 +127,6 @@ const pageRoutes = [
|
|||||||
component: SecurityUsers,
|
component: SecurityUsers,
|
||||||
noSider: false,
|
noSider: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'jobs',
|
|
||||||
exact: true,
|
|
||||||
component: Jobs,
|
|
||||||
noSider: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
component: () => NoMatch,
|
component: () => NoMatch,
|
||||||
|
|||||||
@@ -651,3 +651,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.@{ant-prefix}-empty-img-default{
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
@@ -15,11 +15,13 @@ module.exports = merge(getWebpackCommonConfig(), {
|
|||||||
layout: ['./src/index.tsx'],
|
layout: ['./src/index.tsx'],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CountPlugin({
|
isProd
|
||||||
pathname: 'knowdesign',
|
? new CountPlugin({
|
||||||
startCount: true,
|
pathname: 'knowdesign',
|
||||||
isExportExcel: false,
|
startCount: true,
|
||||||
}),
|
isExportExcel: false,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
||||||
@@ -53,7 +55,7 @@ module.exports = merge(getWebpackCommonConfig(), {
|
|||||||
: []
|
: []
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
],
|
].filter((p) => p),
|
||||||
output: {
|
output: {
|
||||||
path: outPath,
|
path: outPath,
|
||||||
publicPath: isProd ? process.env.PUBLIC_PATH + '/layout/' : '/',
|
publicPath: isProd ? process.env.PUBLIC_PATH + '/layout/' : '/',
|
||||||
@@ -79,11 +81,11 @@ module.exports = merge(getWebpackCommonConfig(), {
|
|||||||
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://localhost:8080/',
|
||||||
},
|
},
|
||||||
'/logi-security/api/v1': {
|
'/logi-security/api/v1': {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/',
|
target: 'http://localhost:8080/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,19 +30,19 @@
|
|||||||
<goal>install-node-and-npm</goal>
|
<goal>install-node-and-npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<nodeVersion>v12.20.0</nodeVersion>
|
<nodeVersion>v12.22.12</nodeVersion>
|
||||||
<npmVersion>6.14.8</npmVersion>
|
<npmVersion>6.14.16</npmVersion>
|
||||||
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
|
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
|
||||||
<npmDownloadRoot>https://registry.npm.taobao.org/npm/-/</npmDownloadRoot>
|
<npmDownloadRoot>https://registry.npm.taobao.org/npm/-/</npmDownloadRoot>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>npm install</id>
|
<id>npm run i</id>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>npm</goal>
|
<goal>npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<arguments>install</arguments>
|
<arguments>run i</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -ex
|
|
||||||
#检测node版本
|
|
||||||
echo "node version: " `node -v`
|
|
||||||
echo "npm version: " `npm -v`
|
|
||||||
|
|
||||||
pwd=`pwd`
|
|
||||||
echo "start install"
|
|
||||||
# npm run clean
|
|
||||||
npm run i
|
|
||||||
echo "install success"
|
|
||||||
|
|
||||||
echo "start build"
|
|
||||||
rm -rf pub/
|
|
||||||
lerna run build
|
|
||||||
echo "build success"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -ex
|
|
||||||
# rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json yarn.lock packages/*/yarn.lock
|
|
||||||
|
|
||||||
#检测node版本
|
|
||||||
echo "node version: " `node -v`
|
|
||||||
echo "npm version: " `npm -v`
|
|
||||||
|
|
||||||
pwd=`pwd`
|
|
||||||
echo "start develop"
|
|
||||||
npm run i
|
|
||||||
|
|
||||||
echo "本地开发请打开 http://localhost:8000"
|
|
||||||
|
|
||||||
lerna run start
|
|
||||||
echo "start success"
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user