mirror of
https://github.com/didi/KnowStreaming.git
synced 2025-12-24 20:22:12 +08:00
feat: 支持 Zookeeper 模块
This commit is contained in:
@@ -1345,9 +1345,9 @@
|
||||
}
|
||||
},
|
||||
"@knowdesign/icons": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.0.tgz",
|
||||
"integrity": "sha512-7c+h2TSbh2ihTkXIivuO+DddNC5wG7hVv9SS4ccmkvTKls2ZTLitPu+U0wpufDxPhkPMaKEQfsECsVJ+7jLMiw==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@knowdesign/icons": "^1.0.0",
|
||||
"@knowdesign/icons": "^1.0.2",
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"dotenv": "^16.0.1",
|
||||
|
||||
@@ -1388,9 +1388,9 @@
|
||||
}
|
||||
},
|
||||
"@knowdesign/icons": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.1.tgz",
|
||||
"integrity": "sha512-EI3s25BJt+Slv7/t6B3K3zv7I6TKkk2Wf1y68zuxK80MMkWf8lqqUtyAZbFDoPUfXAjw6vHktMBH44gbMHMRFA==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"dependencies": {
|
||||
"@ant-design/compatible": "^1.0.8",
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@knowdesign/icons": "^1.0.1",
|
||||
"@knowdesign/icons": "^1.0.2",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
||||
BIN
km-console/packages/layout-clusters-fe/src/assets/beta-tag.png
Normal file
BIN
km-console/packages/layout-clusters-fe/src/assets/beta-tag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -26,7 +26,6 @@ const OptionsDefault = [
|
||||
|
||||
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
const {
|
||||
hasCustomScope,
|
||||
customScopeList: customList,
|
||||
scopeName = '',
|
||||
scopeLabel = '自定义范围',
|
||||
@@ -129,79 +128,75 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
{hasCustomScope && (
|
||||
<div className="flx_r">
|
||||
<h6 className="time_title">{scopeLabel}</h6>
|
||||
<div className="custom-scope">
|
||||
<div className="check-row">
|
||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
全选
|
||||
</Checkbox>
|
||||
<Input
|
||||
className="search-input"
|
||||
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||
size="small"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed-height">
|
||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||
<Row gutter={[10, 12]}>
|
||||
{customList
|
||||
.filter((item) => item.label.includes(scopeSearchValue))
|
||||
.map((item) => (
|
||||
<Col span={12} key={item.value}>
|
||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
<div className="flx_r">
|
||||
<h6 className="time_title">{scopeLabel}</h6>
|
||||
<div className="custom-scope">
|
||||
<div className="check-row">
|
||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
全选
|
||||
</Checkbox>
|
||||
<Input
|
||||
className="search-input"
|
||||
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||
size="small"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed-height">
|
||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||
<Row gutter={[10, 12]}>
|
||||
{customList
|
||||
.filter((item) => item.label.includes(scopeSearchValue))
|
||||
.map((item) => (
|
||||
<Col span={12} key={item.value}>
|
||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
|
||||
<div className="btn-con">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
className="btn-sure"
|
||||
onClick={customSure}
|
||||
disabled={checkedListTemp?.length > 0 ? false : true}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
<Button size="small" onClick={customCancel}>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
<div className="btn-con">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
className="btn-sure"
|
||||
onClick={customSure}
|
||||
disabled={checkedListTemp?.length > 0 ? false : true}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
<Button size="small" onClick={customCancel}>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div id="d-node-scope">
|
||||
<div className="scope-title">{scopeName}筛选:</div>
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
visible={popVisible}
|
||||
content={clickContent}
|
||||
placement="bottomRight"
|
||||
overlayClassName={`d-node-scope-popover ${hasCustomScope ? 'large-size' : ''}`}
|
||||
onVisibleChange={visibleChange}
|
||||
>
|
||||
<span className="input-span">
|
||||
<Input
|
||||
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
||||
value={inputValue}
|
||||
readOnly={true}
|
||||
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
</>
|
||||
<div id="d-node-scope">
|
||||
<div className="scope-title">{scopeName}筛选:</div>
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
visible={popVisible}
|
||||
content={clickContent}
|
||||
placement="bottomRight"
|
||||
overlayClassName="d-node-scope-popover large-size"
|
||||
onVisibleChange={visibleChange}
|
||||
>
|
||||
<span className="input-span">
|
||||
<Input
|
||||
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
||||
value={inputValue}
|
||||
readOnly={true}
|
||||
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ export interface IcustomScope {
|
||||
}
|
||||
|
||||
export interface InodeScopeModule {
|
||||
hasCustomScope: boolean;
|
||||
customScopeList: IcustomScope[];
|
||||
scopeName?: string;
|
||||
scopeLabel?: string;
|
||||
@@ -87,7 +86,6 @@ const GRID_SIZE_OPTIONS = [
|
||||
const MetricOperateBar = ({
|
||||
metricSelect,
|
||||
nodeScopeModule = {
|
||||
hasCustomScope: false,
|
||||
customScopeList: [],
|
||||
},
|
||||
hideNodeScope = false,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfi
|
||||
const METRIC_DASHBOARD_REQ_MAP = {
|
||||
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
|
||||
[MetricType.Topic]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Topic),
|
||||
[MetricType.Zookeeper]: (clusterId: string) => '',
|
||||
[MetricType.Zookeeper]: (clusterId: string) => api.getZookeeperMetrics(clusterId),
|
||||
};
|
||||
|
||||
export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) =>
|
||||
|
||||
@@ -108,10 +108,10 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
||||
startTime,
|
||||
endTime,
|
||||
metricsNames: selectedMetricNames,
|
||||
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
||||
},
|
||||
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
|
||||
? {
|
||||
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
||||
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
|
||||
? null
|
||||
: curHeaderOptions.scopeData.data,
|
||||
@@ -233,8 +233,8 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
||||
<div id="dashboard-drag-chart" className="topic-dashboard">
|
||||
<ChartOperateBar
|
||||
onChange={ksHeaderChange}
|
||||
hideNodeScope={dashboardType === MetricType.Zookeeper}
|
||||
nodeScopeModule={{
|
||||
hasCustomScope: !(dashboardType === MetricType.Zookeeper),
|
||||
customScopeList: scopeList,
|
||||
scopeName: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
|
||||
scopeLabel: `自定义 ${
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
|
||||
|
||||
import { ClusterRunState } from '@src/pages/MutliClusterPage/List';
|
||||
const pkgJson = require('../../package');
|
||||
export const systemKey = pkgJson.ident;
|
||||
|
||||
export const leftMenus = (clusterId?: string) => ({
|
||||
export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
name: `${systemKey}`,
|
||||
icon: 'icon-jiqun',
|
||||
path: `cluster/${clusterId}`,
|
||||
@@ -11,12 +12,12 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
{
|
||||
name: 'cluster',
|
||||
path: 'cluster',
|
||||
icon: 'icon-Cluster',
|
||||
icon: 'icon-Cluster1',
|
||||
},
|
||||
{
|
||||
name: 'broker',
|
||||
path: 'broker',
|
||||
icon: 'icon-Brokers',
|
||||
icon: 'icon-Brokers1',
|
||||
children: [
|
||||
{
|
||||
name: 'dashbord',
|
||||
@@ -38,7 +39,7 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
{
|
||||
name: 'topic',
|
||||
path: 'topic',
|
||||
icon: 'icon-Topics',
|
||||
icon: 'icon-Topics1',
|
||||
children: [
|
||||
{
|
||||
name: 'dashbord',
|
||||
@@ -52,10 +53,36 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
},
|
||||
],
|
||||
},
|
||||
clusterRunState && clusterRunState !== ClusterRunState.Raft
|
||||
? {
|
||||
name: (intl: any) => {
|
||||
return (
|
||||
<div className="menu-item-with-beta-tag">
|
||||
<span>{intl.formatMessage({ id: 'menu.cluster.zookeeper' })}</span>
|
||||
<div className="beta-tag"></div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
path: 'zookeeper',
|
||||
icon: 'icon-Zookeeper',
|
||||
children: [
|
||||
{
|
||||
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.dashboard' })}</span>,
|
||||
path: '',
|
||||
icon: '#icon-luoji',
|
||||
},
|
||||
{
|
||||
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.servers' })}</span>,
|
||||
path: 'servers',
|
||||
icon: 'icon-Jobs',
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
name: 'consumer-group',
|
||||
path: 'consumers',
|
||||
icon: 'icon-ConsumerGroups',
|
||||
icon: 'icon-Consumer',
|
||||
// children: [
|
||||
// {
|
||||
// name: 'operating-state',
|
||||
@@ -72,7 +99,7 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
{
|
||||
name: 'operation',
|
||||
path: 'operation',
|
||||
icon: 'icon-Jobs',
|
||||
icon: 'icon-Operation',
|
||||
children: [
|
||||
process.env.BUSINESS_VERSION
|
||||
? {
|
||||
@@ -92,7 +119,7 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
? {
|
||||
name: 'produce-consume',
|
||||
path: 'testing',
|
||||
icon: 'icon-a-ProduceConsume',
|
||||
icon: 'icon-Message',
|
||||
permissionPoint: [ClustersPermissionMap.TEST_CONSUMER, ClustersPermissionMap.TEST_PRODUCER],
|
||||
children: [
|
||||
{
|
||||
@@ -113,7 +140,7 @@ export const leftMenus = (clusterId?: string) => ({
|
||||
{
|
||||
name: 'security',
|
||||
path: 'security',
|
||||
icon: 'icon-ACLs',
|
||||
icon: 'icon-Security',
|
||||
children: [
|
||||
{
|
||||
name: 'acls',
|
||||
@@ -259,6 +259,21 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-with-beta-tag {
|
||||
display: flex;
|
||||
.beta-tag {
|
||||
width: 26px;
|
||||
margin-left: 4px;
|
||||
background: no-repeat center/26px 15px url('./assets/beta-tag.png');
|
||||
}
|
||||
}
|
||||
|
||||
.dcloud-menu-item-selected .menu-item-with-beta-tag .beta-tag {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.empty-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -51,6 +51,10 @@ export default {
|
||||
|
||||
[`menu.${systemKey}.jobs`]: 'Job',
|
||||
|
||||
[`menu.${systemKey}.zookeeper`]: 'Zookeeper',
|
||||
[`menu.${systemKey}.zookeeper.dashboard`]: 'Overview',
|
||||
[`menu.${systemKey}.zookeeper.servers`]: 'Servers',
|
||||
|
||||
'access.cluster': '接入集群',
|
||||
'access.cluster.low.version.tip': '监测到当前Version较低,建议维护Zookeeper信息以便得到更好的产品体验',
|
||||
'edit.cluster': '编辑集群',
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import SwitchTab from '@src/components/SwitchTab';
|
||||
|
||||
const isJSON = (str: string) => {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const ZKData = ({ nodeData }: { nodeData: string }) => {
|
||||
const [showMode, setShowMode] = useState('default');
|
||||
return (
|
||||
<>
|
||||
<div className="zk-detail-layout-right-content-format">
|
||||
<SwitchTab defaultKey={showMode} onChange={(key) => setShowMode(key)}>
|
||||
<SwitchTab.TabItem key="default">
|
||||
<div style={{ padding: '0 10px' }}>原始格式</div>
|
||||
</SwitchTab.TabItem>
|
||||
<SwitchTab.TabItem key="JSON">
|
||||
<div style={{ padding: '0 10px' }}>JSON格式</div>
|
||||
</SwitchTab.TabItem>
|
||||
</SwitchTab>
|
||||
</div>
|
||||
{showMode === 'default' && (
|
||||
<div className={'zk-detail-layout-right-content-data'}>
|
||||
{isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
|
||||
</div>
|
||||
)}
|
||||
{showMode === 'JSON' && (
|
||||
<div className={'zk-detail-layout-right-content-code'}>
|
||||
<CodeMirror
|
||||
className={'zk-detail-layout-right-content-code-data'}
|
||||
value={isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
|
||||
options={{
|
||||
mode: 'application/json',
|
||||
lineNumbers: true,
|
||||
lineWrapper: true,
|
||||
autoCloseBrackets: true,
|
||||
smartIndent: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
onBeforeChange={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKData;
|
||||
@@ -0,0 +1,188 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Drawer, Utils, AppContainer, Spin, Empty } from 'knowdesign';
|
||||
import ZKDetailMenu from './Sider';
|
||||
import Api from '@src/api';
|
||||
import ZKInfo from './Info';
|
||||
import ZKData from './Data';
|
||||
const { request } = Utils;
|
||||
import './index.less';
|
||||
import { DataNode } from './config';
|
||||
|
||||
const ZookeeperDetail = ({ visible, setVisible }: { visible: boolean; setVisible: (visible: boolean) => void }) => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
// const { visible, setVisible } = props;
|
||||
const [detailLoading, setDetailLoading] = useState(true);
|
||||
const [isDetail, setIsDetail] = useState(true);
|
||||
const [detailInfoLoading, setDetailInfoLoading] = useState(false);
|
||||
const [pathList, setPathList] = useState([]);
|
||||
const [node, setNode] = useState<any>({});
|
||||
const [idenKey, setIdenKey] = useState([]);
|
||||
const [siderWidth, setSiderWidth] = useState(200);
|
||||
const [startPageX, setStartPageX] = useState(0);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [detailTreeData, setDetailTreeData] = useState([]);
|
||||
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setPathList([]);
|
||||
setNode({});
|
||||
setIdenKey([]);
|
||||
};
|
||||
|
||||
const siderMouseDown = (e: { pageX: number }) => {
|
||||
setStartPageX(e.pageX);
|
||||
setDragging(true);
|
||||
};
|
||||
|
||||
const siderMouseMove = (e: { pageX: number }) => {
|
||||
const currentSiderWidth = siderWidth + e.pageX - startPageX;
|
||||
|
||||
if (currentSiderWidth < 200) {
|
||||
setSiderWidth(200);
|
||||
} else if (currentSiderWidth > 320) {
|
||||
setSiderWidth(320);
|
||||
} else {
|
||||
setSiderWidth(currentSiderWidth);
|
||||
}
|
||||
|
||||
setStartPageX(e.pageX);
|
||||
};
|
||||
|
||||
const siderMouseUp = () => {
|
||||
setDragging(false);
|
||||
};
|
||||
|
||||
const rootClick = () => {
|
||||
setPathList([]);
|
||||
setIdenKey([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 第一次加载不触发详情信息的loading
|
||||
!detailLoading && setDetailInfoLoading(true);
|
||||
visible &&
|
||||
request(Api.getZookeeperNodeData(+global?.clusterInfo?.id), {
|
||||
params: { path: '/' + pathList.map((item: DataNode) => item.title).join('/') },
|
||||
})
|
||||
.then((res) => {
|
||||
setNode(res || {});
|
||||
})
|
||||
.catch(() => {
|
||||
setNode({});
|
||||
})
|
||||
.finally(() => {
|
||||
setDetailInfoLoading(false);
|
||||
});
|
||||
}, [pathList]);
|
||||
|
||||
useEffect(() => {
|
||||
setDetailLoading(true);
|
||||
visible &&
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: { path: '/', keyword: '' } })
|
||||
// zkDetailInfo()
|
||||
.then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${index}`,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
if (newData.length > 0) {
|
||||
setIsDetail(false);
|
||||
setDetailTreeData(newData);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setDetailLoading(false);
|
||||
});
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
push={false}
|
||||
title={'Zookeeper 详情'}
|
||||
width={1080}
|
||||
placement="right"
|
||||
onClose={onClose}
|
||||
visible={visible}
|
||||
className="zookeeper-detail-drawer"
|
||||
destroyOnClose
|
||||
maskClosable={false}
|
||||
>
|
||||
<Spin spinning={detailLoading}>
|
||||
{isDetail ? (
|
||||
<div className="zk-detail-empty">
|
||||
<Empty image={Empty.PRESENTED_IMAGE_CUSTOM} description="暂无数据" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="zk-detail-layout">
|
||||
<div className="zk-detail-layout-left" style={{ width: siderWidth + 'px' }}>
|
||||
<div className="zk-detail-layout-left-title">目录结构</div>
|
||||
<div className="zk-detail-layout-left-content">
|
||||
{visible && (
|
||||
<ZKDetailMenu
|
||||
setDetailInfoLoading={setDetailInfoLoading}
|
||||
detailTreeData={detailTreeData}
|
||||
setPathList={setPathList}
|
||||
setIdenKey={setIdenKey}
|
||||
idenKey={idenKey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="zk-detail-layout-resizer" style={{ left: siderWidth + 'px' }} onMouseDown={siderMouseDown}>
|
||||
{dragging && <div className="resize-mask" onMouseMove={siderMouseMove} onMouseUp={siderMouseUp} />}
|
||||
</div>
|
||||
<div className="zk-detail-layout-right">
|
||||
<div className="zk-detail-layout-right-title">详细信息</div>
|
||||
<div className="zk-detail-layout-right-content">
|
||||
{visible && (
|
||||
<Spin spinning={detailInfoLoading}>
|
||||
<div className="zk-detail-layout-right-content-countheight">
|
||||
<div className="zk-detail-layout-right-content-path">
|
||||
<span onClick={rootClick}>{node.namespace}</span>
|
||||
{pathList.length > 0 &&
|
||||
pathList.map((item, index) => {
|
||||
if (item.key === idenKey[0]) {
|
||||
return (
|
||||
<span key={index}>
|
||||
{' '}
|
||||
/
|
||||
<a key={index} onClick={() => setIdenKey([item.key])}>
|
||||
{item.title}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span key={index}>
|
||||
{' '}
|
||||
/
|
||||
<span key={index} onClick={() => setIdenKey([item.key])}>
|
||||
{item.title}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* <div>{'/' + pathList.map((item: any) => item.title).join(' / ') || '/'}</div> */}
|
||||
<div className="zk-detail-layout-right-content-info">
|
||||
<ZKInfo siderWidth={siderWidth} nodeInfo={node?.stat || {}} />
|
||||
</div>
|
||||
<ZKData nodeData={node?.data || ''} />
|
||||
</div>
|
||||
</Spin>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Spin>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperDetail;
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Descriptions } from 'knowdesign';
|
||||
import moment from 'moment';
|
||||
const ZKInfo = ({ nodeInfo, siderWidth }: any) => {
|
||||
const smallStyle = {
|
||||
width: 82,
|
||||
};
|
||||
return (
|
||||
<Descriptions
|
||||
style={{ fontSize: '13px' }}
|
||||
column={siderWidth >= 270 ? 2 : 3}
|
||||
labelStyle={{
|
||||
display: 'flex',
|
||||
textAlign: 'right',
|
||||
justifyContent: 'end',
|
||||
color: '#74788D',
|
||||
fontSize: '13px',
|
||||
width: siderWidth >= 270 ? 144 : 100,
|
||||
}}
|
||||
contentStyle={{ fontSize: '13px' }}
|
||||
>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="aversion">
|
||||
{nodeInfo.aversion || nodeInfo.aversion === 0 ? nodeInfo.aversion : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ctime">{nodeInfo.ctime || nodeInfo.ctime === 0 ? nodeInfo.ctime : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="ctime-pretty">
|
||||
{nodeInfo.ctime ? moment(nodeInfo.ctime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="cversion">
|
||||
{nodeInfo.cversion || nodeInfo.cversion === 0 ? nodeInfo.cversion : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="czxid">{nodeInfo.czxid || nodeInfo.czxid === 0 ? nodeInfo.czxid : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="dataLength">
|
||||
{nodeInfo.dataLength || nodeInfo.dataLength === 0 ? nodeInfo.dataLength : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mtime">
|
||||
{nodeInfo.mtime || nodeInfo.mtime === 0 ? nodeInfo.mtime : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="mtime-pretty">
|
||||
{nodeInfo.mtime ? moment(nodeInfo.mtime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="numChildren">
|
||||
{nodeInfo.numChildren || nodeInfo.numChildren === 0 ? nodeInfo.numChildren : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mzxid">
|
||||
{nodeInfo.mzxid || nodeInfo.mzxid === 0 ? nodeInfo.mzxid : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ephemeralOwner">
|
||||
{nodeInfo.ephemeralOwner || nodeInfo.ephemeralOwner === 0 ? nodeInfo.ephemeralOwner : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item style={{ paddingBottom: '12px' }} label="pzxid">
|
||||
{nodeInfo.pzxid || nodeInfo.pzxid === 0 ? nodeInfo.pzxid : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} style={{ paddingBottom: '12px' }} label="version">
|
||||
{nodeInfo.version || nodeInfo.version === 0 ? nodeInfo.version : '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKInfo;
|
||||
@@ -0,0 +1,179 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Tree, SearchInput, AppContainer, Utils } from 'knowdesign';
|
||||
import { DataNode, DetailMenuType, getPathByKey, updateTreeData } from './config';
|
||||
import Api from '@src/api';
|
||||
|
||||
const { request } = Utils;
|
||||
|
||||
const ZKDetailMenu = (props: DetailMenuType) => {
|
||||
const { detailTreeData, setDetailInfoLoading, setPathList, setIdenKey, idenKey } = props;
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [treeData, setTreeData] = useState<DataNode[]>(detailTreeData);
|
||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
|
||||
const [childrenClose, setChildrenClose] = useState<boolean>(false);
|
||||
// const [searchValue, setSearchValue] = useState<string>('');
|
||||
|
||||
// const treeRef = useRef();
|
||||
|
||||
// 处理参数
|
||||
const getParams = (key: string, searchValue?: string) => {
|
||||
const path =
|
||||
'/' +
|
||||
getPathByKey(key, treeData)
|
||||
.map((item: any) => item.title)
|
||||
.join('/');
|
||||
return {
|
||||
path,
|
||||
keyword: searchValue ? searchValue : '',
|
||||
};
|
||||
};
|
||||
|
||||
const onSelect = (selectedKeys: string[]) => {
|
||||
// 控制右侧详情内容的Loading
|
||||
setDetailInfoLoading(true);
|
||||
setIdenKey(selectedKeys);
|
||||
};
|
||||
|
||||
const onLoadData = ({ key, children = null }: any) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 节点关闭,在展开要重新发送请求,以保证节点的准确性
|
||||
if (children && !childrenClose) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(key) })
|
||||
.then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${key}-${index}`,
|
||||
};
|
||||
})
|
||||
: [
|
||||
{
|
||||
title: '暂无子节点',
|
||||
key: `${key}-${1}`,
|
||||
disabled: true,
|
||||
selectable: false,
|
||||
isLeaf: true,
|
||||
},
|
||||
];
|
||||
setAutoExpandParent(true);
|
||||
setTreeData((origin: DataNode[]) => updateTreeData(origin, key, newData));
|
||||
})
|
||||
.finally(() => {
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const searchChange = (e: string) => {
|
||||
if (idenKey[0] && idenKey[0].length > 0) {
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(idenKey[0], e) }).then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${idenKey[0]}-${index}`,
|
||||
};
|
||||
})
|
||||
: [
|
||||
// 如果查询不到节点或者所查询的父节点下没有子节点人为插入一个节点
|
||||
{
|
||||
title: e ? '未搜索到相关节点' : '暂无子节点',
|
||||
key: `${idenKey[0]}-${0}`,
|
||||
disabled: true,
|
||||
selectable: false,
|
||||
isLeaf: true,
|
||||
},
|
||||
];
|
||||
|
||||
setTreeData((origin: DataNode[]) => {
|
||||
return updateTreeData(origin, idenKey[0], newData);
|
||||
});
|
||||
// 筛选打开的节点中非选中节点的其他节点(排除选中节点以及选中节点的叶子节点)
|
||||
const filterExpandedKeys = expandedKeys.filter((item) => item.slice(0, idenKey[0].length) !== idenKey[0]);
|
||||
// 将当前选中的节点再次合并
|
||||
const newExpandedKeys = [...filterExpandedKeys, ...idenKey];
|
||||
setExpandedKeys(newExpandedKeys);
|
||||
setLoadedKeys(newExpandedKeys);
|
||||
setAutoExpandParent(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 展开收起
|
||||
const onExpand = (keys: any, arg: any) => {
|
||||
const { node, expanded } = arg;
|
||||
let filterExpandedKeys = keys;
|
||||
let newLoadKeys = loadedKeys;
|
||||
setChildrenClose(false);
|
||||
if (!expanded) {
|
||||
idenKey[0] !== node.key && (idenKey[0] as string)?.slice(0, node.key.length) === node.key && setIdenKey([]);
|
||||
filterExpandedKeys = keys.filter((item: string) => {
|
||||
return item !== node.key && item.slice(0, node.key.length) !== node.key;
|
||||
});
|
||||
newLoadKeys = loadedKeys.filter((i: string) => i.slice(0, node.key.length) !== node.key);
|
||||
setChildrenClose(true);
|
||||
}
|
||||
setAutoExpandParent(false);
|
||||
setExpandedKeys(filterExpandedKeys);
|
||||
setLoadedKeys(newLoadKeys);
|
||||
};
|
||||
|
||||
const onLoad = (loadedKeys: string[]) => {
|
||||
setLoadedKeys(loadedKeys);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// treeRef?.current?.scrollTo({ key: idenKey[0] });
|
||||
const pathKey = getPathByKey(idenKey[0], treeData);
|
||||
setPathList(pathKey);
|
||||
}, [idenKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (detailTreeData.length > 0) {
|
||||
setTreeData(detailTreeData);
|
||||
}
|
||||
}, [detailTreeData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchInput
|
||||
onSearch={searchChange}
|
||||
attrs={{
|
||||
placeholder: '在当前节点进行搜索',
|
||||
// onChange: searchChange,
|
||||
size: 'small',
|
||||
style: {
|
||||
marginBottom: '15px',
|
||||
},
|
||||
// value: searchValue,
|
||||
// onChange: (value: string) => setSearchValue(value),
|
||||
}}
|
||||
/>
|
||||
<div className="zk-detail-layout-left-content-text">
|
||||
<Tree
|
||||
// ref={treeRef}
|
||||
// height={300}
|
||||
onLoad={onLoad}
|
||||
loadData={onLoadData}
|
||||
loadedKeys={loadedKeys}
|
||||
expandedKeys={expandedKeys}
|
||||
selectedKeys={idenKey}
|
||||
onExpand={onExpand}
|
||||
autoExpandParent={autoExpandParent}
|
||||
onSelect={onSelect}
|
||||
treeData={treeData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKDetailMenu;
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react';
|
||||
import { Tag } from 'knowdesign';
|
||||
export interface DetailMenuType {
|
||||
detailTreeData: any;
|
||||
setDetailInfoLoading: (loading: boolean) => void;
|
||||
setPathList: (pathList: DataNode[]) => void;
|
||||
setIdenKey: (idenKey: string[]) => void;
|
||||
idenKey: string[];
|
||||
}
|
||||
export interface DataNode {
|
||||
title: string;
|
||||
key: string;
|
||||
isLeaf?: boolean;
|
||||
children?: DataNode[];
|
||||
}
|
||||
|
||||
// 角色
|
||||
const roleType: any = {
|
||||
leader: 'Leader',
|
||||
follower: 'Follower',
|
||||
ovsever: 'Obsever',
|
||||
};
|
||||
|
||||
export const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
|
||||
return list.map((node) => {
|
||||
if (node.key === key) {
|
||||
return {
|
||||
...node,
|
||||
children,
|
||||
};
|
||||
}
|
||||
if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
children: updateTreeData(node.children, key, children),
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
};
|
||||
|
||||
export const getZookeeperColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Host',
|
||||
dataIndex: 'host',
|
||||
key: 'host',
|
||||
width: 200,
|
||||
render: (t: string, r: any) => {
|
||||
return (
|
||||
<span>
|
||||
{t}
|
||||
{r?.status ? <Tag className="tag-success">Live</Tag> : <Tag className="tag-error">Down</Tag>}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Port',
|
||||
dataIndex: 'port',
|
||||
key: 'port',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Role',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
width: 200,
|
||||
render(t: string, r: any) {
|
||||
return (
|
||||
<Tag
|
||||
style={{
|
||||
background: t === 'leader' ? 'rgba(85,110,230,0.10)' : t === 'follower' ? 'rgba(0,192,162,0.10)' : '#fff3e4',
|
||||
color: t === 'leader' ? '#556EE6' : t === 'follower' ? '#00C0A2' : '#F58342',
|
||||
padding: '3px 6px',
|
||||
}}
|
||||
>
|
||||
{roleType[t]}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
||||
export const defaultPagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
position: 'bottomRight',
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
|
||||
};
|
||||
|
||||
export const getPathByKey = (curKey: string, data: DataNode[]) => {
|
||||
/** 存放搜索到的树节点到顶部节点的路径节点 */
|
||||
let result: any[] = [];
|
||||
|
||||
const traverse = (curKey: string, path: any[], data: DataNode[]) => {
|
||||
// 树为空时,不执行函数
|
||||
if (data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历存放树的数组
|
||||
for (const item of data) {
|
||||
// 遍历的数组元素存入path参数数组中
|
||||
path.push(item);
|
||||
// 如果目的节点的id值等于当前遍历元素的节点id值
|
||||
if (item.key === curKey) {
|
||||
// 把获取到的节点路径数组path赋值到result数组
|
||||
result = JSON.parse(JSON.stringify(path));
|
||||
return;
|
||||
}
|
||||
|
||||
// 当前元素的children是数组
|
||||
const children = Array.isArray(item.children) ? item.children : [];
|
||||
// 递归遍历子数组内容
|
||||
traverse(curKey, path, children);
|
||||
// 利用回溯思想,当没有在当前叶树找到目的节点,依次删除存入到的path数组路径
|
||||
path.pop();
|
||||
}
|
||||
};
|
||||
traverse(curKey, [], data);
|
||||
// 返回找到的树节点路径
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
.zookeeper-detail-drawer {
|
||||
.zk-detail-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #eff2f7;
|
||||
height: calc(100vh - 90px);
|
||||
}
|
||||
.zk-detail-layout {
|
||||
display: flex;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #eff2f7;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
&-left,
|
||||
&-right {
|
||||
&-title {
|
||||
background-color: rgba(116, 120, 141, 0.04);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: @font-family-bold;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 2px solid #eff2f7;
|
||||
}
|
||||
&-content {
|
||||
height: calc(100vh - 128px);
|
||||
}
|
||||
}
|
||||
&-left {
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
&-content {
|
||||
padding: 10px;
|
||||
&-text {
|
||||
height: calc(100vh - 190px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.dcloud-tree-title {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
.dcloud-tree-treenode {
|
||||
font-size: 13px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.dcloud-tree-node-content-wrapper {
|
||||
&:hover:not(.dcloud-tree-node-content-wrapper-normal) {
|
||||
background: #f1f3ff !important;
|
||||
color: #556ee6;
|
||||
}
|
||||
}
|
||||
.dcloud-tree .dcloud-tree-node-content-wrapper.dcloud-tree-node-selected {
|
||||
background-color: transparent;
|
||||
color: #556ee6;
|
||||
}
|
||||
}
|
||||
&-resizer {
|
||||
.resize-mask {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
cursor: col-resize;
|
||||
}
|
||||
border: 1px solid #eff2f7;
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
cursor: col-resize;
|
||||
&:hover {
|
||||
border: 1px solid #556ee6;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
&-content {
|
||||
padding: 12px 20px 16px;
|
||||
overflow: auto;
|
||||
&-info {
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
border-radius: 8px;
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
&-countheight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 156px);
|
||||
}
|
||||
&-path {
|
||||
margin-bottom: 16px;
|
||||
|
||||
& > *:hover {
|
||||
color: #556ee6;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&-format {
|
||||
margin: 12px 0 6px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
}
|
||||
&-data {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px 0;
|
||||
}
|
||||
&-code {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
&-data {
|
||||
height: 100% !important;
|
||||
}
|
||||
.cm-s-default {
|
||||
height: 100% !important;
|
||||
// border-radius: 8px !important;
|
||||
overflow: hidden;
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import { useParams, useHistory, useLocation } from 'react-router-dom';
|
||||
import { ProTable, Button, Utils, AppContainer, SearchInput } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '../../api';
|
||||
import { getZookeeperColumns, defaultPagination } from './config';
|
||||
import { tableHeaderPrefix } from '@src/constants/common';
|
||||
import ZookeeperDetail from './Detail';
|
||||
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import './index.less';
|
||||
const { request } = Utils;
|
||||
|
||||
const ZookeeperList: React.FC = () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [detailVisible, setDetailVisible] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
const [searchKeywords, setSearchKeywords] = useState('');
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
|
||||
// 请求接口获取数据
|
||||
const genData = async ({ pageNo, pageSize, filters, sorter }: any) => {
|
||||
if (global?.clusterInfo?.id === undefined) return;
|
||||
|
||||
setLoading(true);
|
||||
const params = {
|
||||
searchKeywords: searchKeywords.slice(0, 128),
|
||||
pageNo,
|
||||
pageSize,
|
||||
};
|
||||
|
||||
request(API.getZookeeperList(global?.clusterInfo?.id), { method: 'POST', data: params })
|
||||
.then((res: any) => {
|
||||
setPagination({
|
||||
current: res.pagination?.pageNo,
|
||||
pageSize: res.pagination?.pageSize,
|
||||
total: res.pagination?.total,
|
||||
});
|
||||
const newData =
|
||||
res?.bizData.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
...item?.latestMetrics?.metrics,
|
||||
};
|
||||
}) || [];
|
||||
setData(newData);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
genData({
|
||||
pageNo: 1,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}, [searchKeywords]);
|
||||
|
||||
return (
|
||||
<div key="brokerList" className="brokerList">
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Zookeeper', aHref: `/cluster/${global?.clusterInfo?.id}/zookepper` },
|
||||
{ label: 'Servers', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ margin: '12px 0' }}>
|
||||
<ZookeeperCard />
|
||||
</div>
|
||||
<div className="clustom-table-content">
|
||||
<div className={tableHeaderPrefix}>
|
||||
<div className={`${tableHeaderPrefix}-left`}>
|
||||
<div
|
||||
className={`${tableHeaderPrefix}-left-refresh`}
|
||||
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
|
||||
>
|
||||
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${tableHeaderPrefix}-right`}>
|
||||
<SearchInput
|
||||
onSearch={setSearchKeywords}
|
||||
attrs={{
|
||||
placeholder: '请输入Host',
|
||||
style: { width: '248px', borderRiadus: '8px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" onClick={() => setDetailVisible(true)}>
|
||||
查看Zookeeper详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ProTable
|
||||
key="zookeeper-table"
|
||||
showQueryForm={false}
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
rowKey: 'zookeeper_list',
|
||||
loading: loading,
|
||||
columns: getZookeeperColumns(),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: { y: 'calc(100vh - 400px)' },
|
||||
bordered: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{<ZookeeperDetail visible={detailVisible} setVisible={setDetailVisible} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperList;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { MetricType } from '@src/api';
|
||||
import DraggableCharts from '@src/components/DraggableCharts';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import { AppContainer } from 'knowdesign';
|
||||
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
|
||||
|
||||
const ZookeeperDashboard = (): JSX.Element => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Zookeeper', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<ZookeeperCard />
|
||||
<DraggableCharts type={MetricType.Zookeeper} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperDashboard;
|
||||
@@ -7,6 +7,7 @@ import { leftMenus, systemKey } from '@src/constants/menu';
|
||||
import { ClustersPermissionMap } from './CommonConfig';
|
||||
import { getLicenseInfo } from '@src/constants/common';
|
||||
import { licenseEventBus } from '@src/constants/axiosConfig';
|
||||
import { ClusterRunState } from './MutliClusterPage/List';
|
||||
|
||||
export const NoMatch = <Redirect to="/404" />;
|
||||
|
||||
@@ -35,6 +36,13 @@ const LayoutContainer = () => {
|
||||
const [showSider, setShowSider] = useState<boolean>(!(notCurrentSystemKey || hasNoSiderPage));
|
||||
const [handledLeftMenus, setHandledLeftMenus] = useState(leftMenus());
|
||||
|
||||
const forbidenPaths = (path: string) => {
|
||||
// Raft 模式运行的集群没有 ZK 页面
|
||||
if (path.includes('zookeeper') && global.clusterInfo?.runState === ClusterRunState.Raft) {
|
||||
history.replace('/404');
|
||||
}
|
||||
};
|
||||
|
||||
const isShowMenu = useCallback(
|
||||
(nodes: ClustersPermissionMap | ClustersPermissionMap[]) => {
|
||||
let isAllow = false;
|
||||
@@ -67,6 +75,7 @@ const LayoutContainer = () => {
|
||||
if (permissionNode) {
|
||||
// 判断用户是否有当前页面的权限
|
||||
if (global.hasPermission(permissionNode)) {
|
||||
forbidenPaths(path);
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
// 用户没有当前页面权限,跳转到多集群首页
|
||||
@@ -77,6 +86,7 @@ const LayoutContainer = () => {
|
||||
return Promise.reject(false);
|
||||
}
|
||||
}
|
||||
forbidenPaths(path);
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
[global.clusterInfo, global.hasPermission, global.getMetricDefine]
|
||||
@@ -86,12 +96,14 @@ const LayoutContainer = () => {
|
||||
const notCurrentSystemKey = window.location.pathname.split('/')?.[1] !== systemKey;
|
||||
const hasNoSiderPage = noSiderPages.findIndex((item) => item.path === getCurrentPathname(pathname)) > -1;
|
||||
setShowSider(notCurrentSystemKey || hasNoSiderPage ? false : true);
|
||||
|
||||
if (pathname.startsWith('/cluster') || pathname === '/') {
|
||||
const items = pathname.split('/');
|
||||
const clusterId = items[2];
|
||||
setHandledLeftMenus(leftMenus(clusterId));
|
||||
clusterId !== global.clusterInfo?.id &&
|
||||
setHandledLeftMenus(leftMenus(clusterId, pathname === '/' ? undefined : global.clusterInfo?.runState));
|
||||
}
|
||||
}, [pathname]);
|
||||
}, [pathname, global.clusterInfo]);
|
||||
|
||||
return (
|
||||
<div id="sub-system" style={{ display: 'flex' }}>
|
||||
|
||||
@@ -22,6 +22,9 @@ import SecurityACLs from './SecurityACLs';
|
||||
import SecurityUsers from './SecurityUsers';
|
||||
import LoadRebalance from './LoadRebalance';
|
||||
|
||||
import Zookeeper from './Zookeeper';
|
||||
import ZookeeperDashboard from './ZookeeperDashboard';
|
||||
|
||||
const pageRoutes = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -115,6 +118,18 @@ const pageRoutes = [
|
||||
component: Jobs,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'zookeeper',
|
||||
exact: true,
|
||||
component: ZookeeperDashboard,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'zookeeper/servers',
|
||||
exact: true,
|
||||
component: Zookeeper,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'security/acls',
|
||||
exact: true,
|
||||
|
||||
Reference in New Issue
Block a user