mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-05 13:08:48 +08:00
feat: 支持 Zookeeper 模块
This commit is contained in:
@@ -1345,9 +1345,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@knowdesign/icons": {
|
"@knowdesign/icons": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||||
"integrity": "sha512-7c+h2TSbh2ihTkXIivuO+DddNC5wG7hVv9SS4ccmkvTKls2ZTLitPu+U0wpufDxPhkPMaKEQfsECsVJ+7jLMiw==",
|
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ant-design/colors": "^6.0.0",
|
"@ant-design/colors": "^6.0.0",
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"build": "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": {
|
||||||
"@knowdesign/icons": "^1.0.0",
|
"@knowdesign/icons": "^1.0.2",
|
||||||
"babel-preset-react-app": "^10.0.0",
|
"babel-preset-react-app": "^10.0.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
|
|||||||
@@ -1388,9 +1388,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@knowdesign/icons": {
|
"@knowdesign/icons": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||||
"integrity": "sha512-EI3s25BJt+Slv7/t6B3K3zv7I6TKkk2Wf1y68zuxK80MMkWf8lqqUtyAZbFDoPUfXAjw6vHktMBH44gbMHMRFA==",
|
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ant-design/colors": "^6.0.0",
|
"@ant-design/colors": "^6.0.0",
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/compatible": "^1.0.8",
|
"@ant-design/compatible": "^1.0.8",
|
||||||
"@ant-design/icons": "^4.6.2",
|
"@ant-design/icons": "^4.6.2",
|
||||||
"@knowdesign/icons": "^1.0.1",
|
"@knowdesign/icons": "^1.0.2",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@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 NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||||
const {
|
const {
|
||||||
hasCustomScope,
|
|
||||||
customScopeList: customList,
|
customScopeList: customList,
|
||||||
scopeName = '',
|
scopeName = '',
|
||||||
scopeLabel = '自定义范围',
|
scopeLabel = '自定义范围',
|
||||||
@@ -129,79 +128,75 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
{hasCustomScope && (
|
<div className="flx_r">
|
||||||
<div className="flx_r">
|
<h6 className="time_title">{scopeLabel}</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}>
|
全选
|
||||||
全选
|
</Checkbox>
|
||||||
</Checkbox>
|
<Input
|
||||||
<Input
|
className="search-input"
|
||||||
className="search-input"
|
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||||
suffix={<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)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="fixed-height">
|
||||||
<div className="fixed-height">
|
<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) => 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>
|
</Col>
|
||||||
</Col>
|
))}
|
||||||
))}
|
</Row>
|
||||||
</Row>
|
</Checkbox.Group>
|
||||||
</Checkbox.Group>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="btn-con">
|
<div className="btn-con">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
className="btn-sure"
|
className="btn-sure"
|
||||||
onClick={customSure}
|
onClick={customSure}
|
||||||
disabled={checkedListTemp?.length > 0 ? false : true}
|
disabled={checkedListTemp?.length > 0 ? false : true}
|
||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" onClick={customCancel}>
|
<Button size="small" onClick={customCancel}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<div id="d-node-scope">
|
||||||
<div id="d-node-scope">
|
<div className="scope-title">{scopeName}筛选:</div>
|
||||||
<div className="scope-title">{scopeName}筛选:</div>
|
<Popover
|
||||||
<Popover
|
trigger={['click']}
|
||||||
trigger={['click']}
|
visible={popVisible}
|
||||||
visible={popVisible}
|
content={clickContent}
|
||||||
content={clickContent}
|
placement="bottomRight"
|
||||||
placement="bottomRight"
|
overlayClassName="d-node-scope-popover large-size"
|
||||||
overlayClassName={`d-node-scope-popover ${hasCustomScope ? 'large-size' : ''}`}
|
onVisibleChange={visibleChange}
|
||||||
onVisibleChange={visibleChange}
|
>
|
||||||
>
|
<span className="input-span">
|
||||||
<span className="input-span">
|
<Input
|
||||||
<Input
|
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
||||||
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
value={inputValue}
|
||||||
value={inputValue}
|
readOnly={true}
|
||||||
readOnly={true}
|
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
||||||
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
/>
|
||||||
/>
|
</span>
|
||||||
</span>
|
</Popover>
|
||||||
</Popover>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export interface IcustomScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface InodeScopeModule {
|
export interface InodeScopeModule {
|
||||||
hasCustomScope: boolean;
|
|
||||||
customScopeList: IcustomScope[];
|
customScopeList: IcustomScope[];
|
||||||
scopeName?: string;
|
scopeName?: string;
|
||||||
scopeLabel?: string;
|
scopeLabel?: string;
|
||||||
@@ -87,7 +86,6 @@ const GRID_SIZE_OPTIONS = [
|
|||||||
const MetricOperateBar = ({
|
const MetricOperateBar = ({
|
||||||
metricSelect,
|
metricSelect,
|
||||||
nodeScopeModule = {
|
nodeScopeModule = {
|
||||||
hasCustomScope: false,
|
|
||||||
customScopeList: [],
|
customScopeList: [],
|
||||||
},
|
},
|
||||||
hideNodeScope = false,
|
hideNodeScope = false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfi
|
|||||||
const METRIC_DASHBOARD_REQ_MAP = {
|
const METRIC_DASHBOARD_REQ_MAP = {
|
||||||
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
|
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
|
||||||
[MetricType.Topic]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Topic),
|
[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) =>
|
export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) =>
|
||||||
|
|||||||
@@ -108,10 +108,10 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
metricsNames: selectedMetricNames,
|
metricsNames: selectedMetricNames,
|
||||||
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
|
||||||
},
|
},
|
||||||
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
|
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
|
||||||
? {
|
? {
|
||||||
|
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
||||||
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
|
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
|
||||||
? null
|
? null
|
||||||
: curHeaderOptions.scopeData.data,
|
: curHeaderOptions.scopeData.data,
|
||||||
@@ -233,8 +233,8 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
|||||||
<div id="dashboard-drag-chart" className="topic-dashboard">
|
<div id="dashboard-drag-chart" className="topic-dashboard">
|
||||||
<ChartOperateBar
|
<ChartOperateBar
|
||||||
onChange={ksHeaderChange}
|
onChange={ksHeaderChange}
|
||||||
|
hideNodeScope={dashboardType === MetricType.Zookeeper}
|
||||||
nodeScopeModule={{
|
nodeScopeModule={{
|
||||||
hasCustomScope: !(dashboardType === MetricType.Zookeeper),
|
|
||||||
customScopeList: scopeList,
|
customScopeList: scopeList,
|
||||||
scopeName: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
|
scopeName: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
|
||||||
scopeLabel: `自定义 ${
|
scopeLabel: `自定义 ${
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
|
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
|
||||||
|
import { ClusterRunState } from '@src/pages/MutliClusterPage/List';
|
||||||
const pkgJson = require('../../package');
|
const pkgJson = require('../../package');
|
||||||
export const systemKey = pkgJson.ident;
|
export const systemKey = pkgJson.ident;
|
||||||
|
|
||||||
export const leftMenus = (clusterId?: string) => ({
|
export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||||
name: `${systemKey}`,
|
name: `${systemKey}`,
|
||||||
icon: 'icon-jiqun',
|
icon: 'icon-jiqun',
|
||||||
path: `cluster/${clusterId}`,
|
path: `cluster/${clusterId}`,
|
||||||
@@ -11,12 +12,12 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
{
|
{
|
||||||
name: 'cluster',
|
name: 'cluster',
|
||||||
path: 'cluster',
|
path: 'cluster',
|
||||||
icon: 'icon-Cluster',
|
icon: 'icon-Cluster1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'broker',
|
name: 'broker',
|
||||||
path: 'broker',
|
path: 'broker',
|
||||||
icon: 'icon-Brokers',
|
icon: 'icon-Brokers1',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'dashbord',
|
name: 'dashbord',
|
||||||
@@ -38,7 +39,7 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
{
|
{
|
||||||
name: 'topic',
|
name: 'topic',
|
||||||
path: 'topic',
|
path: 'topic',
|
||||||
icon: 'icon-Topics',
|
icon: 'icon-Topics1',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'dashbord',
|
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',
|
name: 'consumer-group',
|
||||||
path: 'consumers',
|
path: 'consumers',
|
||||||
icon: 'icon-ConsumerGroups',
|
icon: 'icon-Consumer',
|
||||||
// children: [
|
// children: [
|
||||||
// {
|
// {
|
||||||
// name: 'operating-state',
|
// name: 'operating-state',
|
||||||
@@ -72,7 +99,7 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
{
|
{
|
||||||
name: 'operation',
|
name: 'operation',
|
||||||
path: 'operation',
|
path: 'operation',
|
||||||
icon: 'icon-Jobs',
|
icon: 'icon-Operation',
|
||||||
children: [
|
children: [
|
||||||
process.env.BUSINESS_VERSION
|
process.env.BUSINESS_VERSION
|
||||||
? {
|
? {
|
||||||
@@ -92,7 +119,7 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
? {
|
? {
|
||||||
name: 'produce-consume',
|
name: 'produce-consume',
|
||||||
path: 'testing',
|
path: 'testing',
|
||||||
icon: 'icon-a-ProduceConsume',
|
icon: 'icon-Message',
|
||||||
permissionPoint: [ClustersPermissionMap.TEST_CONSUMER, ClustersPermissionMap.TEST_PRODUCER],
|
permissionPoint: [ClustersPermissionMap.TEST_CONSUMER, ClustersPermissionMap.TEST_PRODUCER],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -113,7 +140,7 @@ export const leftMenus = (clusterId?: string) => ({
|
|||||||
{
|
{
|
||||||
name: 'security',
|
name: 'security',
|
||||||
path: 'security',
|
path: 'security',
|
||||||
icon: 'icon-ACLs',
|
icon: 'icon-Security',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'acls',
|
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 {
|
.empty-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ export default {
|
|||||||
|
|
||||||
[`menu.${systemKey}.jobs`]: 'Job',
|
[`menu.${systemKey}.jobs`]: 'Job',
|
||||||
|
|
||||||
|
[`menu.${systemKey}.zookeeper`]: 'Zookeeper',
|
||||||
|
[`menu.${systemKey}.zookeeper.dashboard`]: 'Overview',
|
||||||
|
[`menu.${systemKey}.zookeeper.servers`]: 'Servers',
|
||||||
|
|
||||||
'access.cluster': '接入集群',
|
'access.cluster': '接入集群',
|
||||||
'access.cluster.low.version.tip': '监测到当前Version较低,建议维护Zookeeper信息以便得到更好的产品体验',
|
'access.cluster.low.version.tip': '监测到当前Version较低,建议维护Zookeeper信息以便得到更好的产品体验',
|
||||||
'edit.cluster': '编辑集群',
|
'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 { ClustersPermissionMap } from './CommonConfig';
|
||||||
import { getLicenseInfo } from '@src/constants/common';
|
import { getLicenseInfo } from '@src/constants/common';
|
||||||
import { licenseEventBus } from '@src/constants/axiosConfig';
|
import { licenseEventBus } from '@src/constants/axiosConfig';
|
||||||
|
import { ClusterRunState } from './MutliClusterPage/List';
|
||||||
|
|
||||||
export const NoMatch = <Redirect to="/404" />;
|
export const NoMatch = <Redirect to="/404" />;
|
||||||
|
|
||||||
@@ -35,6 +36,13 @@ const LayoutContainer = () => {
|
|||||||
const [showSider, setShowSider] = useState<boolean>(!(notCurrentSystemKey || hasNoSiderPage));
|
const [showSider, setShowSider] = useState<boolean>(!(notCurrentSystemKey || hasNoSiderPage));
|
||||||
const [handledLeftMenus, setHandledLeftMenus] = useState(leftMenus());
|
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(
|
const isShowMenu = useCallback(
|
||||||
(nodes: ClustersPermissionMap | ClustersPermissionMap[]) => {
|
(nodes: ClustersPermissionMap | ClustersPermissionMap[]) => {
|
||||||
let isAllow = false;
|
let isAllow = false;
|
||||||
@@ -67,6 +75,7 @@ const LayoutContainer = () => {
|
|||||||
if (permissionNode) {
|
if (permissionNode) {
|
||||||
// 判断用户是否有当前页面的权限
|
// 判断用户是否有当前页面的权限
|
||||||
if (global.hasPermission(permissionNode)) {
|
if (global.hasPermission(permissionNode)) {
|
||||||
|
forbidenPaths(path);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
} else {
|
} else {
|
||||||
// 用户没有当前页面权限,跳转到多集群首页
|
// 用户没有当前页面权限,跳转到多集群首页
|
||||||
@@ -77,6 +86,7 @@ const LayoutContainer = () => {
|
|||||||
return Promise.reject(false);
|
return Promise.reject(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
forbidenPaths(path);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
[global.clusterInfo, global.hasPermission, global.getMetricDefine]
|
[global.clusterInfo, global.hasPermission, global.getMetricDefine]
|
||||||
@@ -86,12 +96,14 @@ const LayoutContainer = () => {
|
|||||||
const notCurrentSystemKey = window.location.pathname.split('/')?.[1] !== systemKey;
|
const notCurrentSystemKey = window.location.pathname.split('/')?.[1] !== systemKey;
|
||||||
const hasNoSiderPage = noSiderPages.findIndex((item) => item.path === getCurrentPathname(pathname)) > -1;
|
const hasNoSiderPage = noSiderPages.findIndex((item) => item.path === getCurrentPathname(pathname)) > -1;
|
||||||
setShowSider(notCurrentSystemKey || hasNoSiderPage ? false : true);
|
setShowSider(notCurrentSystemKey || hasNoSiderPage ? false : true);
|
||||||
|
|
||||||
if (pathname.startsWith('/cluster') || pathname === '/') {
|
if (pathname.startsWith('/cluster') || pathname === '/') {
|
||||||
const items = pathname.split('/');
|
const items = pathname.split('/');
|
||||||
const clusterId = items[2];
|
const clusterId = items[2];
|
||||||
setHandledLeftMenus(leftMenus(clusterId));
|
clusterId !== global.clusterInfo?.id &&
|
||||||
|
setHandledLeftMenus(leftMenus(clusterId, pathname === '/' ? undefined : global.clusterInfo?.runState));
|
||||||
}
|
}
|
||||||
}, [pathname]);
|
}, [pathname, global.clusterInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="sub-system" style={{ display: 'flex' }}>
|
<div id="sub-system" style={{ display: 'flex' }}>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import SecurityACLs from './SecurityACLs';
|
|||||||
import SecurityUsers from './SecurityUsers';
|
import SecurityUsers from './SecurityUsers';
|
||||||
import LoadRebalance from './LoadRebalance';
|
import LoadRebalance from './LoadRebalance';
|
||||||
|
|
||||||
|
import Zookeeper from './Zookeeper';
|
||||||
|
import ZookeeperDashboard from './ZookeeperDashboard';
|
||||||
|
|
||||||
const pageRoutes = [
|
const pageRoutes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -115,6 +118,18 @@ const pageRoutes = [
|
|||||||
component: Jobs,
|
component: Jobs,
|
||||||
noSider: false,
|
noSider: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'zookeeper',
|
||||||
|
exact: true,
|
||||||
|
component: ZookeeperDashboard,
|
||||||
|
noSider: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'zookeeper/servers',
|
||||||
|
exact: true,
|
||||||
|
component: Zookeeper,
|
||||||
|
noSider: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'security/acls',
|
path: 'security/acls',
|
||||||
exact: true,
|
exact: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user