add step of creating new cluster

This commit is contained in:
silenceqi 2021-08-27 10:52:25 +08:00
parent 77fc87c3dc
commit 62a61ec2de
18 changed files with 517 additions and 108 deletions

View File

@ -1,7 +1,7 @@
elasticsearch:
- name: default
enabled: true
endpoint: https://192.168.3.98:9200
endpoint: http://127.0.0.1:9200
basic_auth:
username: elastic
password: ZBdkVQUUdF1Sir4X4BGB
@ -29,6 +29,7 @@ elastic:
init_template: true
template_name: ".infini-search-center"
index_prefix: ".infini-search-center_"
# load_remote_elasticsearch_configs: true
search-center:
ui_path: .public

View File

@ -442,6 +442,12 @@ export default [
name: 'cluster',
component: './System/Cluster/Index',
},
{
path: '/system/cluster/regist',
name: 'registCluster',
component: './System/Cluster/Step',
hideInMenu: true
},
{
path: '/system/cluster/edit',
name: 'editCluster',

View File

@ -29,6 +29,8 @@ import { IndexPatternCreationOption } from '../../types';
import { CreateButton } from '../../create_button';
import { Illustration } from './assets/index_pattern_illustration';
import { ManagementAppMountParams } from '../../../../../management/public';
import Link from 'umi/link';
import Exception from '@/components/Exception';
interface Props {
canSave: boolean;
@ -53,9 +55,9 @@ export const EmptyIndexPatternPrompt = ({
horizontalPosition="center"
>
<EuiFlexGroup gutterSize="xl" alignItems="center" direction="rowReverse" wrap>
<EuiFlexItem grow={1} className="inpEmptyIndexPatternPrompt__illustration">
{/* <EuiFlexItem grow={1} className="inpEmptyIndexPatternPrompt__illustration">
<Illustration />
</EuiFlexItem>
</EuiFlexItem> */}
<EuiFlexItem grow={2} className="inpEmptyIndexPatternPrompt__text">
<EuiText grow={false}>
<h2>

View File

@ -123,6 +123,7 @@ export default {
'menu.system': 'SYSTEM',
'menu.system.cluster': 'CLUSTERS',
'menu.system.registCluster': 'REGIST CLUSTER',
'menu.system.editCluster': 'EDIT CLUSTER',
'menu.system.settings': 'SETTINGS',
'menu.system.settings.global': 'GLOBAL',

View File

@ -130,7 +130,8 @@ export default {
'menu.system': '系统管理',
'menu.system.cluster': '集群管理',
'menu.system.editCluster': '集群编辑',
'menu.system.registCluster': '集群注册',
'menu.system.editCluster': '集群修改',
'menu.system.settings': '系统设置',
'menu.system.settings.global': '全局设置',
'menu.system.settings.gateway': '网关设置',

View File

@ -146,7 +146,7 @@ const vstyle = {
const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading}) => {
// const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]);
const [isPaused, setIsPaused] = useState(true);
const [refreshInterval, setRefreshInterval] = useState();
const [refreshInterval, setRefreshInterval] = useState(10000);
const onTimeChange = ({ start, end }) => {
onChange({

View File

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

View File

@ -53,7 +53,7 @@ class ClusterForm extends React.Component{
//console.log(values);
let newVals = {
name: values.name,
endpoint: values.endpoint,
host: values.host,
basic_auth: {
username: values.username,
password: values.password,
@ -61,7 +61,8 @@ class ClusterForm extends React.Component{
description: values.description,
enabled: values.enabled,
monitored: values.monitored,
order: values.order,
schema: values.isTLS === true ? 'https': 'http',
// order: values.order,
}
if(clusterConfig.editMode === 'NEW') {
dispatch({
@ -125,7 +126,6 @@ class ClusterForm extends React.Component{
router.push('/system/cluster');
}}>返回</Button>]}
>
{/* <NewCluster/> */}
<Form {...formItemLayout}>
<Form.Item label="集群名称">
{getFieldDecorator('name', {
@ -138,20 +138,31 @@ class ClusterForm extends React.Component{
],
})(<Input autoComplete='off' placeholder="cluster-name" />)}
</Form.Item>
<Form.Item label="集群 URL">
{getFieldDecorator('endpoint', {
initialValue: editValue.endpoint,
<Form.Item label="集群地址">
{getFieldDecorator('host', {
initialValue: editValue.host,
rules: [
{
type: 'url', //https://github.com/yiminghe/async-validator#type
message: 'The input is not valid url!',
type: 'string',
pattern: /^[\w\.]+\:\d+$/, //(https?:\/\/)?
message: '请输入域名或 IP 地址和端口号',
},
{
required: true,
message: 'Please input cluster name!',
message: '请输入域名或 IP 地址和端口号!',
},
],
})(<Input placeholder="http://127.0.0.1:9200" />)}
})(<Input placeholder="127.0.0.1:9200" />)}
</Form.Item>
<Form.Item label="TLS">
{getFieldDecorator('isTLS', {
initialValue: editValue?.schema === "https",
})(
<Switch
defaultChecked={editValue?.schema === "https"}
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
<Form.Item label="是否需要身份验证">
<Switch
@ -177,22 +188,17 @@ class ClusterForm extends React.Component{
})(<Input.Password />)}
</Form.Item>
</div>):''}
{/* <Form.Item label="Elasticsearch ">
{getFieldDecorator('version', {
initialValue: editValue.version || '',
})(<Input readOnly={true} />)}
</Form.Item> */}
<Form.Item label="排序权重">
{/* <Form.Item label="">
{getFieldDecorator('order', {
initialValue: editValue.order || 0,
})(<InputNumber />)}
</Form.Item>
</Form.Item> */}
<Form.Item label="描述">
{getFieldDecorator('description', {
initialValue: editValue.description,
})(<Input.TextArea placeholder="集群应用描述" />)}
</Form.Item>
<Form.Item label="是否启用">
{/* <Form.Item label="">
{getFieldDecorator('enabled', {
valuePropName: 'checked',
initialValue: typeof editValue.enabled === 'undefined' ? true: editValue.enabled,
@ -200,7 +206,7 @@ class ClusterForm extends React.Component{
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
</Form.Item> */}
<Form.Item label="启用监控">
{getFieldDecorator('monitored', {
valuePropName: 'checked',
@ -214,9 +220,6 @@ class ClusterForm extends React.Component{
<Button type="primary" onClick={this.handleSubmit}>
{editMode === 'NEW' ? '注册': '保存'}
</Button>
{/* <Button style={{marginLeft:20}}>
测试连接
</Button> */}
</Form.Item>
</Form>
</Card>

View File

@ -40,14 +40,14 @@ class Index extends React.Component {
}
}, {
title: '部署环境',
dataIndex: 'business_department',
key: 'business_department',
dataIndex: 'deploy_env',
key: 'deploy_env',
render: ()=>{
return 'PROD'
}
},{
title: '程序版本',
dataIndex: 'status.version',
dataIndex: 'version',
key: 'elasticsearch_version',
// render: (data)=>{
// return
@ -212,7 +212,7 @@ class Index extends React.Component {
onChange={this.handleEnabledChange}
defaultChecked
/></span>
<Link to='/system/cluster/edit' onClick={this.handleNewClick}> <Button type="primary" icon="plus">新建集群</Button></Link>
<Link to='/system/cluster/regist' onClick={this.handleNewClick}> <Button type="primary" icon="plus">注册集群</Button></Link>
</div>
</div>
<Table

View File

@ -1,7 +1,9 @@
import { Steps, Button, message } from 'antd';
import { Steps, Button, message, Spin, Card } from 'antd';
import {connect} from "dva";
import { useState, useRef } from 'react';
import InitialStep from './steps/initial_step';
import {InitialStep, ExtraStep, ResultStep} from './steps';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './step.less';
const { Step } = Steps;
@ -10,7 +12,7 @@ const steps = [
title: '初始化',
},
{
title: '连接',
title: '信息确认',
content: 'Second-content',
},
{
@ -22,24 +24,100 @@ const steps = [
const ClusterStep = ({
current,
changeStep,
dispatch,
history,
}) => {
const initalFormRef = useRef();
const formRef = useRef();
const [clusterConfig, setClusterConfig] = useState({})
const [isLoading, setIsLoading] = useState(false);
// const [clusterInfo, setClusterInfo] = useState({});
const handleConnect = async ()=>{
const result = await formRef.current.validateFields((errors, values) => {
if(errors){
return false;
}
return values;
}).catch((err)=>{
return false;
});
if(!result){
return false
}
setIsLoading(true)
const res = await dispatch({
type: 'clusterConfig/doTryConnect',
payload: {
basic_auth:{
username: result.username,
password: result.password,
},
host: result.host,
schema: result.isTLS === true ? 'https': 'http',
},
});
if(res && !res.error){
setClusterConfig({
...result,
...res,
});
return true;
}else{
setIsLoading(false)
return false;
}
}
const handleCommit = async ()=>{
const result = await formRef.current.validateFields((errors, values) => {
if(errors){
return false;
}
// console.log(values);
return values
});
if(!result){
return fasle;
}
const newVals = {
name: result.name,
version: clusterConfig.version,
host: clusterConfig.host,
basic_auth: {
username: clusterConfig.username || '',
password: clusterConfig.password || '',
},
description: result.description,
enabled: true,
monitored: result.monitored,
schema: clusterConfig.isTLS ? 'https': 'http'
}
setIsLoading(true);
const res = await dispatch({
type: 'clusterConfig/addCluster',
payload: newVals,
});
if(res && !res.error){
return true;
}else{
setIsLoading(false)
return false;
}
}
const next = async () => {
let result
if(current === 0){
result = await initalFormRef.current.validateFields((errors, values) => {
if(errors){
return false;
}
console.log(values)
}).catch((err)=>{
return false;
})
result = await handleConnect();
}else if(current === 1){
result = await handleCommit();
}
if(!result){
return
}
setIsLoading(false)
changeStep(current + 1)
};
@ -47,48 +125,97 @@ const ClusterStep = ({
changeStep(current - 1)
};
const oneMoreClick = ()=>{
setClusterConfig({});
changeStep(0);
}
const goToClusterList = ()=>{
history.push('/system/cluster');
}
const renderContent = (current)=>{
if(current===0){
return <InitialStep ref={initalFormRef} />
return <InitialStep ref={formRef} initialValue={clusterConfig} />
}else if(current === 1){
return <></>
}else{
return null
return <ExtraStep initialValue={clusterConfig} ref={formRef}/>
}else if(current === 2){
return <ResultStep clusterConfig={clusterConfig} oneMoreClick={oneMoreClick}
goToClusterList={goToClusterList}
/>
}
}
return (
<>
<Steps current={current}>
{steps.map(item => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<div className="steps-content">{renderContent(current)}</div>
<div className="steps-action" style={{textAlign:'center'}}>
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => message.success('Processing complete!')}>
完成
</Button>
)}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
const content = (
<div className={styles.pageHeaderContent}>
<p>
输入集群地址和身份验证信息分步创建集群
</p>
<div className={styles.contentLink}>
<a>
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" />{' '}
快速开始
</a>
<a>
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg" />{' '}
产品简介
</a>
<a>
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" />{' '}
产品文档
</a>
</div>
</>
</div>
);
const extraContent = (
<div className={styles.extraImg}>
<img
alt="这是一个标题"
src="https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png"
/>
</div>
);
return (
<PageHeaderWrapper title="集群注册" content={content} extraContent={extraContent}>
<Card>
<Spin spinning={isLoading}>
<div style={{maxWidth:720, margin:'0 auto'}}>
<Steps current={current} style={{marginBottom:24}}>
{steps.map(item => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<div className="steps-content">{renderContent(current)}</div>
<div className="steps-action" style={{textAlign:'center'}}>
{current === 1 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
</div>
</div>
</Spin>
</Card>
</PageHeaderWrapper>
);
};
const NewCluster = ()=>{
const NewCluster = (props)=>{
const {dispatch, history} = props;
const [current, setCurrent] = useState(0);
return <ClusterStep current={current} changeStep={setCurrent} />
return <ClusterStep current={current} changeStep={setCurrent}
history={history}
dispatch={dispatch} />
}
export default connect(({

View File

@ -1,4 +1,5 @@
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig, getClusterStatus} from "@/services/cluster";
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig,
getClusterStatus, tryConnect} from "@/services/cluster";
import {message} from "antd";
import {formatESSearchResult} from '@/lib/elasticsearch/util';
@ -45,6 +46,9 @@ export default {
return false;
}
let {data, total} = yield select(state => state.clusterConfig);
if(!data){
return
}
data.unshift({
...res._source,
id: res._id,
@ -147,6 +151,21 @@ export default {
}
})
return res;
},
*doTryConnect({payload}, {call, put, select}) {
let res = yield call(tryConnect, payload)
if(res.error){
message.error(res.error)
return false;
}
yield put({
type: 'saveData',
payload: {
tempClusterInfo: res,
}
})
return res;
}
},
reducers:{

View File

@ -0,0 +1,26 @@
.extraImg {
margin-top: -60px;
text-align: center;
width: 195px;
img {
width: 100%;
}
}
.pageHeaderContent {
position: relative;
}
.contentLink {
margin-top: 16px;
a {
margin-right: 32px;
img {
width: 24px;
}
}
img {
vertical-align: middle;
margin-right: 8px;
}
}

View File

@ -0,0 +1,88 @@
import {Form, Input, Switch, Icon, InputNumber, Divider, Descriptions} from 'antd';
@Form.create()
export class ExtraStep extends React.Component {
state = {
}
render(){
const {form:{getFieldDecorator}, initialValue} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
return (
<>
<Descriptions column={2} size="small" bordered>
<Descriptions.Item label="集群版本" >
{initialValue?.version}
</Descriptions.Item>
<Descriptions.Item label="健康状态" >
{initialValue?.status}
</Descriptions.Item>
<Descriptions.Item label="节点数" >
{initialValue?.number_of_nodes}
</Descriptions.Item>
<Descriptions.Item label="数据节点数" >
{initialValue?.number_of_data_nodes}
</Descriptions.Item>
<Descriptions.Item label="分片数" >
{initialValue?.active_shards}
</Descriptions.Item>
</Descriptions>
<Divider/>
<Form {...formItemLayout} style={{marginTop:15}} form={this.props.formRef}>
<Form.Item label="集群名称" >
{getFieldDecorator('name', {
initialValue: initialValue?.cluster_name || '',
rules: [
{
required: true,
message: 'Please input cluster name!',
},
],
})(<Input autoComplete='off' placeholder="cluster-name" />)}
</Form.Item>
{/* <Form.Item label="Elasticsearch ">
{getFieldDecorator('version', {
initialValue: initialValue?.version || '',
})(<Input readOnly={true} />)}
</Form.Item> */}
{/* <Form.Item label="">
{getFieldDecorator('order', {
initialValue: 0,
})(<InputNumber />)}
</Form.Item> */}
<Form.Item label="描述">
{getFieldDecorator('description', {
initialValue: '',
})(<Input.TextArea placeholder="集群应用描述" />)}
</Form.Item>
{/* <Form.Item label="">
{getFieldDecorator('enabled', {
valuePropName: 'checked',
initialValue: true,
})(<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item> */}
<Form.Item label="启用监控">
{getFieldDecorator('monitored', {
valuePropName: 'checked',
initialValue: true,
})(<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
</Form>
</>
)
}
}

View File

@ -0,0 +1,3 @@
export * from './initial_step';
export * from './extra_step';
export * from './result_step';

View File

@ -1,10 +1,12 @@
import {Form, Input, Switch, Icon} from 'antd';
import {useState} from 'react';
@Form.create()
class InitialStep extends React.Component {
state = {
needAuth: false,
export class InitialStep extends React.Component {
constructor(props){
super(props);
this.state = {
needAuth: props.initialValue?.username !== undefined,
}
}
handleAuthChange = (val) => {
this.setState({
@ -12,7 +14,7 @@ class InitialStep extends React.Component {
})
}
render(){
const {form:{getFieldDecorator}} = this.props;
const {form:{getFieldDecorator}, initialValue} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
@ -24,34 +26,33 @@ class InitialStep extends React.Component {
},
};
return (
<Form {...formItemLayout} style={{marginTop:15}} form={this.props.formRef}>
<Form.Item label="集群名称" >
{getFieldDecorator('name', {
initialValue: '',
<Form {...formItemLayout} form={this.props.formRef}>
<Form.Item label="集群地址">
{getFieldDecorator('host', {
initialValue: initialValue?.host || '',
rules: [
{
required: true,
message: 'Please input cluster name!',
},
],
})(<Input autoComplete='off' placeholder="cluster-name" />)}
</Form.Item>
<Form.Item label="集群 URL">
{getFieldDecorator('endpoint', {
initialValue: '',
rules: [
{
type: 'url',
message: 'The input is not valid url!',
type: 'string',
pattern: /^[\w\.]+\:\d+$/, //(https?:\/\/)?
message: '请输入域名或 IP 地址和端口号',
},
{
required: true,
message: 'Please input cluster endpoint!',
message: '请输入集群地址!',
},
],
})(<Input placeholder="http://127.0.0.1:9200" />)}
})(<Input placeholder="127.0.0.1:9200" />)}
</Form.Item>
<Form.Item label="是否需要身份验证">
<Form.Item label="TLS">
{getFieldDecorator('isTLS', {
initialValue: initialValue?.isTLS || false,
})(
<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
<Form.Item label="身份验证">
<Switch
defaultChecked={this.state.needAuth}
onChange={this.handleAuthChange}
@ -62,7 +63,7 @@ class InitialStep extends React.Component {
{this.state.needAuth === true ? (<div>
<Form.Item label="用户名">
{getFieldDecorator('username', {
initialValue: '',
initialValue: initialValue?.username || '',
rules: [
{
required: true,
@ -73,7 +74,7 @@ class InitialStep extends React.Component {
</Form.Item>
<Form.Item label="密码" hasFeedback>
{getFieldDecorator('password', {
initialValue: '',
initialValue: initialValue?.password || '',
rules: [
{
required: true,
@ -86,6 +87,4 @@ class InitialStep extends React.Component {
</Form>
)
}
}
export default InitialStep;
}

View File

@ -0,0 +1,63 @@
import Result from '@/components/Result';
import React, { Fragment } from 'react';
import { Button, Row, Col } from 'antd';
import styles from './styles.less';
export const ResultStep = (props)=>{
const {clusterConfig, oneMoreClick, goToClusterList} = props;
const information = (
<div className={styles.information}>
<Row>
<Col xs={24} sm={8} className={styles.label}>
集群名称
</Col>
<Col xs={24} sm={16}>
{clusterConfig?.cluster_name}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
集群版本
</Col>
<Col xs={24} sm={16}>
{clusterConfig?.version}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
集群地址
</Col>
<Col xs={24} sm={16}>
{clusterConfig?.host}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
TLS
</Col>
<Col xs={24} sm={16}>
{clusterConfig?.isTLS ? '是': '否'}
</Col>
</Row>
</div>
);
const actions = (
<Fragment>
<Button type="primary" onClick={oneMoreClick}>
再创建一个集群
</Button>
<Button onClick={goToClusterList}>查看集群列表</Button>
</Fragment>
);
return (
<Result
type="success"
title="创建成功"
description=""
extra={information}
actions={actions}
className={styles.result}
/>
);
}

View File

@ -0,0 +1,62 @@
@import '~antd/lib/style/themes/default.less';
.result {
margin: 0 auto;
max-width: 560px;
padding: 24px 0 8px;
}
.desc {
padding: 0 56px;
// color: @text-color-secondary;
h3 {
font-size: 16px;
margin: 0 0 12px 0;
// color: @text-color-secondary;
line-height: 32px;
}
h4 {
margin: 0 0 4px 0;
// color: @text-color-secondary;
font-size: 14px;
line-height: 22px;
}
p {
margin-top: 0;
margin-bottom: 12px;
line-height: 22px;
}
}
@media screen and (max-width: @screen-md) {
.desc {
padding: 0;
}
}
.information {
line-height: 22px;
:global {
.ant-row:not(:last-child) {
margin-bottom: 24px;
}
}
.label {
// color: @heading-color;
text-align: right;
padding-right: 8px;
@media screen and (max-width: @screen-sm) {
text-align: left;
}
}
}
.money {
font-family: 'Helvetica Neue', sans-serif;
font-weight: 500;
font-size: 20px;
line-height: 14px;
}
.uppercase {
font-size: 12px;
}

View File

@ -16,7 +16,7 @@ export async function getClusterMetrics(params) {
}
export async function createClusterConfig(params) {
return request(`/elasticsearch`, {
return request(`/elasticsearch/`, {
method: 'POST',
body: params,
});
@ -57,4 +57,12 @@ export async function getClusterStatus(params) {
return request(url, {
method: 'GET',
});
}
export async function tryConnect(params) {
let url = `/elasticsearch/try_connect`;
return request(url, {
method: 'POST',
body: params,
});
}