mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-13 03:29:45 +08:00
feat: 支持 Zookeeper 模块
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user