modify cluster manage

This commit is contained in:
silenceqi 2021-08-30 16:19:47 +08:00
parent 62a61ec2de
commit b8f1297a19
12 changed files with 192 additions and 75 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -43,7 +43,7 @@ const ConsoleWrapper = ({
<EuiFlexItem className="conApp__tabsExtension"> <EuiFlexItem className="conApp__tabsExtension">
<RequestStatusBar <RequestStatusBar
requestInProgress={requestInProgress} requestInProgress={requestInProgress}
// selectedCluster={selectedCluster} selectedCluster={selectedCluster}
requestResult={ requestResult={
lastDatum lastDatum
? { ? {

View File

@ -63,8 +63,14 @@ const mapStatusCodeToBadgeColor = (statusCode: number) => {
export const RequestStatusBar: FunctionComponent<Props> = ({ export const RequestStatusBar: FunctionComponent<Props> = ({
requestInProgress, requestInProgress,
requestResult, requestResult,
selectedCluster,
}) => { }) => {
let content: React.ReactNode = null; let content: React.ReactNode = null;
const clusterContent = (<EuiFlexItem grow={false} style={{marginRight:'auto'}}>
<EuiBadge>
{selectedCluster.endpoint}&nbsp;-&nbsp;{selectedCluster.version}
</EuiBadge>
</EuiFlexItem>);
if (requestInProgress) { if (requestInProgress) {
content = ( content = (
@ -122,6 +128,7 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
gutterSize="s" gutterSize="s"
responsive={false} responsive={false}
> >
{clusterContent}
{content} {content}
</EuiFlexGroup> </EuiFlexGroup>
); );

View File

@ -23,7 +23,9 @@ import moment from 'moment';
import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals';
import { getForceNow } from './lib/get_force_now'; import { getForceNow } from './lib/get_force_now';
import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types';
import { calculateBounds, getTime, RefreshInterval, TimeRange } from '../../../common'; import { getTime, RefreshInterval, TimeRange } from '../../../common';
import { calculateBounds } from '../../../common/query/timefilter/get_time';
import { TimeHistoryContract } from './time_history'; import { TimeHistoryContract } from './time_history';
import { IndexPattern } from '../../index_patterns'; import { IndexPattern } from '../../index_patterns';
@ -173,6 +175,9 @@ export class Timefilter {
} }
public calculateBounds(timeRange: TimeRange): TimeRangeBounds { public calculateBounds(timeRange: TimeRange): TimeRangeBounds {
if(typeof calculateBounds !== 'function'){
console.log(typeof(calculateBounds), calculateBounds)
}
return calculateBounds(timeRange, { forceNow: this.getForceNow() }); return calculateBounds(timeRange, { forceNow: this.getForceNow() });
} }

View File

@ -53,6 +53,8 @@ export default {
return { return {
name: item.name, name: item.name,
id: item.id, id: item.id,
endpoint: item.endpoint,
version: item.version,
}; };
}) })

View File

@ -164,7 +164,7 @@ const filterManager = new FilterManager();
const storage = new Storage(localStorage); const storage = new Storage(localStorage);
const queryStringManager = new QueryStringManager(storage); const queryStringManager = new QueryStringManager(storage);
const timefilterConfig = { const timefilterConfig = {
timeDefaults: { from: 'now-1y', to: 'now' }, timeDefaults: { from: 'now-15m', to: 'now' },
refreshIntervalDefaults: { pause: true, value: 10000 }, refreshIntervalDefaults: { pause: true, value: 10000 },
}; };
const timeHistory = new TimeHistory(storage); const timeHistory = new TimeHistory(storage);

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import {Card, Form, Icon, Input, InputNumber, Button, Switch, message} from 'antd'; import {Card, Form, Icon, Input, InputNumber, Button, Switch, message, Spin} from 'antd';
import router from 'umi/router'; import router from 'umi/router';
import styles from './Form.less'; import styles from './Form.less';
import {connect} from "dva"; import {connect} from "dva";
import NewCluster from './Step'; import NewCluster from './Step';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
@Form.create() @Form.create()
@connect(({clusterConfig}) =>({ @connect(({clusterConfig}) =>({
@ -21,6 +22,7 @@ class ClusterForm extends React.Component{
this.state = { this.state = {
confirmDirty: false, confirmDirty: false,
needAuth: needAuth, needAuth: needAuth,
isLoading: false,
} }
} }
componentDidMount() { componentDidMount() {
@ -61,6 +63,7 @@ class ClusterForm extends React.Component{
description: values.description, description: values.description,
enabled: values.enabled, enabled: values.enabled,
monitored: values.monitored, monitored: values.monitored,
version: values.version,
schema: values.isTLS === true ? 'https': 'http', schema: values.isTLS === true ? 'https': 'http',
// order: values.order, // order: values.order,
} }
@ -95,6 +98,40 @@ class ClusterForm extends React.Component{
}) })
} }
tryConnect = async ()=>{
const {dispatch, form} = this.props;
const values = await form.validateFields((errors, values) => {
if(errors){
return false;
}
let newVals = {
name: values.name,
host: values.host,
basic_auth: {
username: values.username,
password: values.password,
},
schema: values.isTLS === true ? 'https': 'http',
}
return values;
});
if(!values){
return
}
this.setState({isLoading: true})
const res = await dispatch({
type: 'clusterConfig/doTryConnect',
payload: values
});
if(res){
message.success('连接成功!')
form.setFieldsValue({
version: res.version
})
}
this.setState({isLoading: false})
}
render() { render() {
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
const formItemLayout = { const formItemLayout = {
@ -121,11 +158,13 @@ class ClusterForm extends React.Component{
}; };
const {editValue, editMode} = this.props.clusterConfig; const {editValue, editMode} = this.props.clusterConfig;
return ( return (
<PageHeaderWrapper>
<Card title={editMode === 'NEW' ? '注册集群': '修改集群配置'} <Card title={editMode === 'NEW' ? '注册集群': '修改集群配置'}
extra={[<Button type="primary" onClick={()=>{ extra={[<Button type="primary" onClick={()=>{
router.push('/system/cluster'); router.push('/system/cluster');
}}>返回</Button>]} }}>返回</Button>]}
> >
<Spin spinning={this.state.isLoading}>
<Form {...formItemLayout}> <Form {...formItemLayout}>
<Form.Item label="集群名称"> <Form.Item label="集群名称">
{getFieldDecorator('name', { {getFieldDecorator('name', {
@ -154,6 +193,13 @@ class ClusterForm extends React.Component{
], ],
})(<Input placeholder="127.0.0.1:9200" />)} })(<Input placeholder="127.0.0.1:9200" />)}
</Form.Item> </Form.Item>
<Form.Item style={{marginBottom:0}}>
{getFieldDecorator('version', {
initialValue: editValue.version,
rules: [
],
})(<Input type="hidden"/>)}
</Form.Item>
<Form.Item label="TLS"> <Form.Item label="TLS">
{getFieldDecorator('isTLS', { {getFieldDecorator('isTLS', {
initialValue: editValue?.schema === "https", initialValue: editValue?.schema === "https",
@ -175,14 +221,14 @@ class ClusterForm extends React.Component{
{this.state.needAuth === true ? (<div> {this.state.needAuth === true ? (<div>
<Form.Item label="用户名"> <Form.Item label="用户名">
{getFieldDecorator('username', { {getFieldDecorator('username', {
initialValue: editValue.basic_auth.username, initialValue: editValue.basic_auth?.username,
rules: [ rules: [
], ],
})(<Input autoComplete='off' />)} })(<Input autoComplete='off' />)}
</Form.Item> </Form.Item>
<Form.Item label="密码" hasFeedback> <Form.Item label="密码" hasFeedback>
{getFieldDecorator('password', { {getFieldDecorator('password', {
initialValue: editValue.basic_auth.password, initialValue: editValue.basic_auth?.password,
rules: [ rules: [
], ],
})(<Input.Password />)} })(<Input.Password />)}
@ -220,9 +266,14 @@ class ClusterForm extends React.Component{
<Button type="primary" onClick={this.handleSubmit}> <Button type="primary" onClick={this.handleSubmit}>
{editMode === 'NEW' ? '注册': '保存'} {editMode === 'NEW' ? '注册': '保存'}
</Button> </Button>
<Button style={{marginLeft: 15}} onClick={this.tryConnect}>
测试连接
</Button>
</Form.Item> </Form.Item>
</Form> </Form>
</Spin>
</Card> </Card>
</PageHeaderWrapper>
) )
} }
} }

View File

@ -2,10 +2,27 @@ import React from 'react';
import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd"; import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd";
import Link from "umi/link"; import Link from "umi/link";
import {connect} from "dva"; import {connect} from "dva";
import {HealthStatusCircle} from './health_status';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './step.less';
import clusterBg from '@/assets/cluster_bg.png';
const HealthStatusCircle = ({status})=>{ const content = (
return <div style={{background: status, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #555'}}></div> <div className={styles.pageHeaderContent}>
} <p>
集群管理通过注册新集群删除集群让您高效的管理多个 Elasticsearch 集群
</p>
</div>
);
const extraContent = (
<div className={styles.extraImg}>
<img
alt="集群管理"
src={clusterBg}
/>
</div>
);
@Form.create() @Form.create()
@connect(({clusterConfig}) =>({ @connect(({clusterConfig}) =>({
@ -18,10 +35,15 @@ class Index extends React.Component {
key: 'name', key: 'name',
},{ },{
title: '健康状态', title: '健康状态',
dataIndex: 'status.health_status', dataIndex: 'id',
key: 'health_status', key: 'health_status',
render: (val)=>{ render: (val)=>{
return <HealthStatusCircle status={val}/> const {clusterStatus} = this.props.clusterConfig;
if(!clusterStatus || !clusterStatus[val]){
return
}
const status = clusterStatus[val].health_status;
return <HealthStatusCircle status={status}/>
} }
},{ },{
title: '所属业务', title: '所属业务',
@ -54,10 +76,14 @@ class Index extends React.Component {
// } // }
},{ },{
title: '节点数', title: '节点数',
dataIndex: 'node_count', dataIndex: 'id',
key: 'mode_count', key: 'mode_count',
render: ()=>{ render: (val)=>{
return 1; const {clusterStatus} = this.props.clusterConfig;
if(!clusterStatus || !clusterStatus[val]){
return
}
return clusterStatus[val].nodes_count;
} }
},{ },{
title: '集群地址', title: '集群地址',
@ -69,7 +95,7 @@ class Index extends React.Component {
dataIndex: 'monitored', dataIndex: 'monitored',
key: 'monitored', key: 'monitored',
render: (val) => { render: (val) => {
return val? '是': '否'; return val? '启用': '关闭';
} }
}, },
// { // {
@ -114,15 +140,26 @@ class Index extends React.Component {
}) })
} }
componentDidMount() { componentDidMount() {
this.fetchData({})
this.fetchClusterStatus();
}
componentWillUnmount(){
if(this.fetchClusterStatusTimer){
clearTimeout(this.fetchClusterStatusTimer);
}
}
fetchClusterStatus = async ()=>{
const {dispatch} = this.props; const {dispatch} = this.props;
dispatch({ const res = await dispatch({
type: 'clusterConfig/fetchClusterStatus', type: 'clusterConfig/fetchClusterStatus',
}).then(()=>{ });
if(typeof this.props.clusterConfig.data === 'undefined') { if(this.fetchClusterStatusTimer){
this.fetchData({}) clearTimeout(this.fetchClusterStatusTimer);
} }
}) if(!res){
return
}
this.fetchClusterStatusTimer = setTimeout(this.fetchClusterStatus, 10000);
} }
handleSearchClick = ()=>{ handleSearchClick = ()=>{
@ -185,43 +222,45 @@ class Index extends React.Component {
}; };
const {data} = this.props.clusterConfig; const {data} = this.props.clusterConfig;
return ( return (
<Card> <PageHeaderWrapper title="集群管理" content={content} extraContent={extraContent}>
<div style={{display:'flex', marginBottom:10, flex:"1 1 auto", justifyContent: 'space-between'}}> <Card>
<div> <div style={{display:'flex', marginBottom:10, flex:"1 1 auto", justifyContent: 'space-between'}}>
<Form> <div>
<Row gutter={{md:24, sm:16}}> <Form>
<Col md={16} sm={20}> <Row gutter={{md:24, sm:16}}>
<Form.Item {...formItemLayout} label="集群名称"> <Col md={16} sm={20}>
{getFieldDecorator('name')(<Input placeholder="please input cluster name" />)} <Form.Item {...formItemLayout} label="集群名称">
</Form.Item> {getFieldDecorator('name')(<Input placeholder="please input cluster name" />)}
</Col> </Form.Item>
<Col md={8} sm={16}> </Col>
<div style={{paddingTop:4}}> <Col md={8} sm={16}>
<Button type="primary" icon="search" onClick={this.handleSearchClick}> <div style={{paddingTop:4}}>
搜索 <Button type="primary" icon="search" onClick={this.handleSearchClick}>
</Button> 搜索
</div> </Button>
</Col> </div>
</Row> </Col>
</Form> </Row>
</Form>
</div>
<div>
{/* <span style={{marginRight:24}}><Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
onChange={this.handleEnabledChange}
defaultChecked
/></span> */}
<Link to='/system/cluster/regist' onClick={this.handleNewClick}> <Button type="primary" icon="plus">注册集群</Button></Link>
</div>
</div> </div>
<div> <Table
<span style={{marginRight:24}}><Switch bordered
checkedChildren={<Icon type="check" />} columns={this.columns}
unCheckedChildren={<Icon type="close" />} dataSource={data}
onChange={this.handleEnabledChange} rowKey='id'
defaultChecked />
/></span> </Card>
<Link to='/system/cluster/regist' onClick={this.handleNewClick}> <Button type="primary" icon="plus">注册集群</Button></Link> </PageHeaderWrapper>
</div>
</div>
<Table
bordered
columns={this.columns}
dataSource={data}
rowKey='id'
/>
</Card>
); );
} }

View File

@ -4,20 +4,19 @@ import { useState, useRef } from 'react';
import {InitialStep, ExtraStep, ResultStep} from './steps'; import {InitialStep, ExtraStep, ResultStep} from './steps';
import PageHeaderWrapper from '@/components/PageHeaderWrapper'; import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './step.less'; import styles from './step.less';
import clusterBg from '@/assets/cluster_bg.png';
const { Step } = Steps; const { Step } = Steps;
const steps = [ const steps = [
{ {
title: '初始化', title: '连接',
}, },
{ {
title: '信息确认', title: '确认',
content: 'Second-content',
}, },
{ {
title: '完成', title: '完成',
content: 'Last-content',
}, },
]; ];
@ -152,7 +151,7 @@ const ClusterStep = ({
<p> <p>
输入集群地址和身份验证信息分步创建集群 输入集群地址和身份验证信息分步创建集群
</p> </p>
<div className={styles.contentLink}> {/* <div className={styles.contentLink}>
<a> <a>
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" />{' '} <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" />{' '}
快速开始 快速开始
@ -165,15 +164,15 @@ const ClusterStep = ({
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" />{' '} <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" />{' '}
产品文档 产品文档
</a> </a>
</div> </div> */}
</div> </div>
); );
const extraContent = ( const extraContent = (
<div className={styles.extraImg}> <div className={styles.extraImg}>
<img <img
alt="这是一个标题" alt="集群管理"
src="https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png" src={clusterBg}
/> />
</div> </div>
); );

View File

@ -0,0 +1,3 @@
export const HealthStatusCircle = ({status})=>{
return <div style={{background: status, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #555'}}></div>
}

View File

@ -21,7 +21,8 @@ export default {
payload: { payload: {
clusterStatus: res clusterStatus: res
} }
}) });
return res;
}, },
*fetchClusterList({payload}, {call, put, select}){ *fetchClusterList({payload}, {call, put, select}){
let res = yield call(searchClusterConfig, payload); let res = yield call(searchClusterConfig, payload);
@ -31,9 +32,9 @@ export default {
} }
res = formatESSearchResult(res) res = formatESSearchResult(res)
const {clusterStatus} = yield select(state => state.clusterConfig); const {clusterStatus} = yield select(state => state.clusterConfig);
for(let item of res.data){ // for(let item of res.data){
item.status= clusterStatus[item.id] // item.status= clusterStatus[item.id]
} // }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: res payload: res

View File

@ -1,4 +1,5 @@
import {Form, Input, Switch, Icon, InputNumber, Divider, Descriptions} from 'antd'; import {Form, Input, Switch, Icon, InputNumber, Divider, Descriptions} from 'antd';
import {HealthStatusCircle} from '../health_status';
@Form.create() @Form.create()
export class ExtraStep extends React.Component { export class ExtraStep extends React.Component {
@ -19,19 +20,28 @@ export class ExtraStep extends React.Component {
return ( return (
<> <>
<Descriptions column={2} size="small" bordered> <Descriptions column={2} size="small" bordered>
<Descriptions.Item label="集群地址" >
{initialValue?.host}
</Descriptions.Item>
<Descriptions.Item label="TLS" >
{initialValue?.isTLS ? <Icon type="lock" style={{color: '#52c41a'}}/> : null}
</Descriptions.Item>
<Descriptions.Item label="集群版本" > <Descriptions.Item label="集群版本" >
{initialValue?.version} {initialValue?.version}
</Descriptions.Item>
<Descriptions.Item label="健康状态" >
{initialValue?.status}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="节点数" > <Descriptions.Item label="身份验证" >
{initialValue?.username ? <Icon type="user"/>: null}
</Descriptions.Item>
<Descriptions.Item label="健康状态" >
<HealthStatusCircle status={initialValue?.status}/>
</Descriptions.Item>
<Descriptions.Item label="节点总数" >
{initialValue?.number_of_nodes} {initialValue?.number_of_nodes}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="数据节点数" > <Descriptions.Item label="数据节点数" >
{initialValue?.number_of_data_nodes} {initialValue?.number_of_data_nodes}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="分片数" > <Descriptions.Item label="分片数" >
{initialValue?.active_shards} {initialValue?.active_shards}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>