modify cluster config

This commit is contained in:
silenceqi 2021-02-09 22:46:15 +08:00
parent 3aeef79fe0
commit 317fbd4e20
10 changed files with 310 additions and 39 deletions

View File

@ -10,6 +10,8 @@ import (
"infini.sh/search-center/model" "infini.sh/search-center/model"
"infini.sh/framework/core/orm" "infini.sh/framework/core/orm"
"net/http" "net/http"
"strings"
"time"
) )
type APIHandler struct { type APIHandler struct {
@ -30,13 +32,18 @@ func (h *APIHandler) HandleCreateClusterAction(w http.ResponseWriter, req *http.
// TODO validate data format // TODO validate data format
esClient := elastic.GetClient(h.Config.Elasticsearch) esClient := elastic.GetClient(h.Config.Elasticsearch)
id := util.GetUUID() id := util.GetUUID()
conf.Created = time.Now()
conf.Updated = conf.Created
conf.ID = id
ir, err := esClient.Index(orm.GetIndexName(model.ClusterConfig{}), "", id, conf) ir, err := esClient.Index(orm.GetIndexName(model.ClusterConfig{}), "", id, conf)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = ir conf.ID = ir.ID
resBody["payload"] = conf
resBody["acknowledged"] = true
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
} }
@ -44,9 +51,9 @@ func (h *APIHandler) HandleUpdateClusterAction(w http.ResponseWriter, req *http.
var conf = map[string]interface{}{} var conf = map[string]interface{}{}
resBody := map[string] interface{}{ resBody := map[string] interface{}{
} }
err := h.DecodeJSON(req, conf) err := h.DecodeJSON(req, &conf)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
@ -55,7 +62,7 @@ func (h *APIHandler) HandleUpdateClusterAction(w http.ResponseWriter, req *http.
indexName := orm.GetIndexName(model.ClusterConfig{}) indexName := orm.GetIndexName(model.ClusterConfig{})
originConf, err := esClient.Get(indexName, "", id) originConf, err := esClient.Get(indexName, "", id)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
@ -66,14 +73,15 @@ func (h *APIHandler) HandleUpdateClusterAction(w http.ResponseWriter, req *http.
} }
source[k] = v source[k] = v
} }
ir, err := esClient.Index(indexName, "", id, source) conf["updated"] = time.Now()
_, err = esClient.Index(indexName, "", id, source)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["acknowledged"] = true resBody["acknowledged"] = true
resBody["payload"] = ir resBody["payload"] = conf
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
} }
@ -84,7 +92,7 @@ func (h *APIHandler) HandleDeleteClusterAction(w http.ResponseWriter, req *http.
esClient := elastic.GetClient(h.Config.Elasticsearch) esClient := elastic.GetClient(h.Config.Elasticsearch)
_, err := esClient.Delete(orm.GetIndexName(model.ClusterConfig{}), "", id) _, err := esClient.Delete(orm.GetIndexName(model.ClusterConfig{}), "", id)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
@ -98,23 +106,28 @@ func (h *APIHandler) HandleSearchClusterAction(w http.ResponseWriter, req *http.
} }
var ( var (
name = h.GetParameterOrDefault(req, "name", "") name = h.GetParameterOrDefault(req, "name", "")
enable = h.GetParameterOrDefault(req, "enable", "") enabled = h.GetParameterOrDefault(req, "enabled", "")
queryDSL = `{"query":{"bool":{"must":[%s, %s]}}}` queryDSL = `{"query":{"bool":{"must":[%s]}}}`
mustBuilder = &strings.Builder{}
) )
if name != ""{ if name != ""{
name = fmt.Sprintf(`{"match":{"name": "%s""}}`, name) mustBuilder.WriteString(fmt.Sprintf(`{"match":{"name": "%s"}}`, name))
} }
if enable != "" { if enabled != "" {
if enable != "true" { if enabled != "true" {
enable = "false" enabled = "false"
} }
enable = fmt.Sprintf(`{"match":{"enable": "%s""}}`, enable) if mustBuilder.Len() > 0 {
mustBuilder.WriteString(",")
}
mustBuilder.WriteString(fmt.Sprintf(`{"match":{"enabled": %s}}`, enabled))
} }
queryDSL = fmt.Sprintf(queryDSL, name, enable)
queryDSL = fmt.Sprintf(queryDSL, mustBuilder.String())
esClient := elastic.GetClient(h.Config.Elasticsearch) esClient := elastic.GetClient(h.Config.Elasticsearch)
res, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(model.ClusterConfig{}), []byte(queryDSL)) res, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(model.ClusterConfig{}), []byte(queryDSL))
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }

View File

@ -71,7 +71,7 @@ func main() {
}, func() { }, func() {
orm.RegisterSchemaWithIndexName(model.Dict{}, "infini-dict") orm.RegisterSchemaWithIndexName(model.Dict{}, "infini-dict")
orm.RegisterSchemaWithIndexName(model.Reindex{}, "infini-reindex") orm.RegisterSchemaWithIndexName(model.Reindex{}, "infini-reindex")
orm.RegisterSchemaWithIndexName(model.Reindex{}, "infini-cluster") orm.RegisterSchemaWithIndexName(model.ClusterConfig{}, "infini-cluster")
}) })
} }

View File

@ -1,11 +1,16 @@
package model package model
import "time"
type ClusterConfig struct { type ClusterConfig struct {
ID string `json:"id" elastic_meta:"_id"` ID string `json:"id" elastic_meta:"_id"`
Name string `json:"name" elastic_mapping:"name:{type:text}"` Name string `json:"name" elastic_mapping:"name:{type:text}"`
Endpoint string `json:"endpoint" elastic_mapping:"name:{type:text}"` Endpoint string `json:"endpoint" elastic_mapping:"endpoint:{type:text}"`
User string `json:"user" elastic_mapping:"name:{type:keyword}"` UserName string `json:"username" elastic_mapping:"username:{type:keyword}"`
Password string `json:"password"elastic_mapping:"name:{type:keyword}" ` Password string `json:"password" elastic_mapping:"password:{type:keyword}" `
Description string `json:"desc" elastic_mapping:"name:{type:text}"` Order int `json:"order" elastic_mapping:"order:{type:integer}"`
Enable bool `json:"enable" elastic_mapping:"name:{type:boolean}"` Description string `json:"description" elastic_mapping:"description:{type:text}"`
Enabled bool `json:"enabled" elastic_mapping:"enabled:{type:boolean}"`
Created time.Time `json:"created" elastic_mapping:"created:{type:date}"`
Updated time.Time `json:"updated" elastic_mapping:"updated:{type:date}"`
} }

View File

@ -303,8 +303,8 @@ export default [
component: './System/Cluster/Index', component: './System/Cluster/Index',
}, },
{ {
path: '/system/cluster/new', path: '/system/cluster/edit',
name: 'new-cluster', name: 'edit-cluster',
component: './System/Cluster/Form', component: './System/Cluster/Form',
hideInMenu: true hideInMenu: true
}, },

View File

@ -1,11 +1,22 @@
import React from 'react'; import React from 'react';
import {Card, Form, Icon, Input, InputNumber, Button, Switch} from 'antd'; import {Card, Form, Icon, Input, InputNumber, Button, Switch} from 'antd';
import router from 'umi/router';
import styles from './Form.less';
import {connect} from "_dva@2.4.1@dva";
@Form.create() @Form.create()
@connect(({clusterConfig}) =>({
clusterConfig
}))
class ClusterForm extends React.Component{ class ClusterForm extends React.Component{
state = { state = {
confirmDirty: false, confirmDirty: false,
} }
componentDidMount() {
//console.log(this.props.clusterConfig.editMode)
}
compareToFirstPassword = (rule, value, callback) => { compareToFirstPassword = (rule, value, callback) => {
const { form } = this.props; const { form } = this.props;
if (value && value !== form.getFieldValue('password')) { if (value && value !== form.getFieldValue('password')) {
@ -22,6 +33,41 @@ class ClusterForm extends React.Component{
} }
callback(); callback();
}; };
handleSubmit = () =>{
const {form, dispatch, clusterConfig} = this.props;
form.validateFields((errors, values) => {
if(errors){
return
}
//console.log(values);
let newVals = {
...values
}
delete(newVals['confirm']);
if(clusterConfig.editMode === 'NEW') {
dispatch({
type: 'clusterConfig/addCluster',
payload: newVals,
}).then(function (rel){
if(rel){
router.push('/system/cluster');
}
});
}else{
newVals.id = clusterConfig.editValue.id;
dispatch({
type: 'clusterConfig/updateCluster',
payload: newVals,
}).then(function (rel){
if(rel){
router.push('/system/cluster');
}
});
}
})
}
render() { render() {
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
const formItemLayout = { const formItemLayout = {
@ -46,21 +92,24 @@ class ClusterForm extends React.Component{
}, },
}, },
}; };
const {editValue, editMode} = this.props.clusterConfig;
return ( return (
<Card> <Card title={editMode === 'NEW' ? '注册集群': '修改集群配置'}>
<Form {...formItemLayout} onSubmit={this.handleSubmit}> <Form {...formItemLayout}>
<Form.Item label="集群名称"> <Form.Item label="集群名称">
{getFieldDecorator('name', { {getFieldDecorator('name', {
initialValue: editValue.name,
rules: [ rules: [
{ {
required: true, required: true,
message: 'Please input cluster name!', message: 'Please input cluster name!',
}, },
], ],
})(<Input />)} })(<Input autoComplete='off' />)}
</Form.Item> </Form.Item>
<Form.Item label="集群 URL"> <Form.Item label="集群 URL">
{getFieldDecorator('endpoint', { {getFieldDecorator('endpoint', {
initialValue: editValue.endpoint,
rules: [ rules: [
{ {
type: 'url', //https://github.com/yiminghe/async-validator#type type: 'url', //https://github.com/yiminghe/async-validator#type
@ -75,12 +124,14 @@ class ClusterForm extends React.Component{
</Form.Item> </Form.Item>
<Form.Item label="ES 用户名"> <Form.Item label="ES 用户名">
{getFieldDecorator('username', { {getFieldDecorator('username', {
initialValue: editValue.username,
rules: [ rules: [
], ],
})(<Input />)} })(<Input autoComplete='off' />)}
</Form.Item> </Form.Item>
<Form.Item label="ES 密码" hasFeedback> <Form.Item label="ES 密码" hasFeedback>
{getFieldDecorator('password', { {getFieldDecorator('password', {
initialValue: editValue.password,
rules: [ rules: [
{ {
validator: this.validateToNextPassword, validator: this.validateToNextPassword,
@ -90,6 +141,7 @@ class ClusterForm extends React.Component{
</Form.Item> </Form.Item>
<Form.Item label="ES 确认密码" hasFeedback> <Form.Item label="ES 确认密码" hasFeedback>
{getFieldDecorator('confirm', { {getFieldDecorator('confirm', {
initialValue: editValue.password,
rules: [ rules: [
{ {
validator: this.compareToFirstPassword, validator: this.compareToFirstPassword,
@ -99,25 +151,26 @@ class ClusterForm extends React.Component{
</Form.Item> </Form.Item>
<Form.Item label="排序权重"> <Form.Item label="排序权重">
{getFieldDecorator('order', { {getFieldDecorator('order', {
initialValue: 0 initialValue: editValue.order,
})(<InputNumber />)} })(<InputNumber />)}
</Form.Item> </Form.Item>
<Form.Item label="描述"> <Form.Item label="描述">
{getFieldDecorator('order', { {getFieldDecorator('description', {
initialValue: editValue.description,
})(<Input.TextArea />)} })(<Input.TextArea />)}
</Form.Item> </Form.Item>
<Form.Item label="是否启用"> <Form.Item label="是否启用">
{getFieldDecorator('enabled', { {getFieldDecorator('enabled', {
valuePropName: 'checked', valuePropName: 'checked',
initialValue: true initialValue: typeof editValue.enabled === 'undefined' ? true: editValue.enabled,
})(<Switch })(<Switch
checkedChildren={<Icon type="check" />} checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />} unCheckedChildren={<Icon type="close" />}
/>)} />)}
</Form.Item> </Form.Item>
<Form.Item {...tailFormItemLayout}> <Form.Item {...tailFormItemLayout}>
<Button type="primary" htmlType="submit"> <Button type="primary" onClick={this.handleSubmit}>
Register {editMode === 'NEW' ? 'Register': 'Save'}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -0,0 +1,3 @@
:global(.ant-form-item){
margin-bottom: 16px;
}

View File

@ -1,8 +1,12 @@
import React from 'react'; import React from 'react';
import {Button, Card, Col, Divider, Form, Input, Row, Table,Switch, Icon} from "antd"; import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm} from "antd";
import Link from "_umi@2.13.16@umi/link"; import Link from "_umi@2.13.16@umi/link";
import {connect} from "dva";
@Form.create() @Form.create()
@connect(({clusterConfig}) =>({
clusterConfig
}))
class Index extends React.Component { class Index extends React.Component {
columns = [{ columns = [{
title: '集群名称', title: '集群名称',
@ -32,8 +36,80 @@ class Index extends React.Component {
title: '是否启用', title: '是否启用',
dataIndex: 'enabled', dataIndex: 'enabled',
key: 'enabled', key: 'enabled',
render: (val) =>{
return val === true ? '是': '否';
}
},{
title: 'Operation',
render: (text, record) => (
<div>
<Link to='/system/cluster/edit' onClick={()=>{this.handleEditClick(record)}}>Edit</Link>
<span><Divider type="vertical" />
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record)}><a key="delete">Delete</a>
</Popconfirm>
</span>
</div>
),
}] }]
fetchData = (params)=>{
const {dispatch} = this.props;
dispatch({
type: 'clusterConfig/fetchClusterList',
payload: params,
})
}
componentDidMount() {
this.fetchData({})
}
handleSearchClick = ()=>{
const {form} = this.props;
this.fetchData({
name: form.getFieldValue('name'),
})
}
handleDeleteClick = (record)=>{
const {dispatch} = this.props;
return dispatch({
type:'clusterConfig/deleteCluster',
payload: {
id: record.id
}
});
}
saveData = (payload)=>{
const {dispatch} = this.props;
return dispatch({
type:'clusterConfig/saveData',
payload: {
...payload
}
});
}
handleNewClick = () => {
this.saveData({
editMode: 'NEW',
editValue: {},
})
}
handleEditClick = (record)=>{
this.saveData({
editMode : 'UPDATE',
editValue: record,
})
}
handleEnabledChange = (enabled) => {
const {form} = this.props;
this.fetchData({
name: form.getFieldValue('name'),
enabled: enabled,
})
}
render() { render() {
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
const formItemLayout = { const formItemLayout = {
@ -41,6 +117,7 @@ class Index extends React.Component {
wrapperCol: { span: 14 }, wrapperCol: { span: 14 },
style: {marginBottom: 0} style: {marginBottom: 0}
}; };
const {data} = this.props.clusterConfig;
return ( return (
<Card> <Card>
<Form> <Form>
@ -52,7 +129,7 @@ class Index extends React.Component {
</Col> </Col>
<Col md={8} sm={8}> <Col md={8} sm={8}>
<div style={{paddingTop:4}}> <div style={{paddingTop:4}}>
<Button type="primary" icon="search" onClick={this.handleSearch}> <Button type="primary" icon="search" onClick={this.handleSearchClick}>
Search Search
</Button> </Button>
</div> </div>
@ -66,15 +143,16 @@ class Index extends React.Component {
<span style={{marginRight:24}}><Switch <span style={{marginRight:24}}><Switch
checkedChildren={<Icon type="check" />} checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />} unCheckedChildren={<Icon type="close" />}
onChange={this.handleEnabledChange}
defaultChecked defaultChecked
/></span> /></span>
<Link to='/system/cluster/new'> <Button type="primary" icon="plus">New</Button></Link> <Link to='/system/cluster/edit' onClick={this.handleNewClick}> <Button type="primary" icon="plus">New</Button></Link>
</div>} </div>}
bordered={false}> bordered={false}>
<Table <Table
bordered bordered
columns={this.columns} columns={this.columns}
dataSource={[]} dataSource={data}
rowKey='id' rowKey='id'
/> />
</Card> </Card>

View File

@ -0,0 +1,68 @@
import {createClusterConfig,searchClusterConfig, updateClusterConfig,deleteClusterConfig} from "@/services/clusterConfig";
import {message} from "antd";
import {formatESSearchResult} from '@/lib/elasticsearch/util';
export default {
namespace: 'clusterConfig',
state: {
editMode: '',
editValue: {},
},
effects:{
*fetchClusterList({payload}, {call, put}){
let res = yield call(searchClusterConfig, payload);
if(res.error){
message.error(res.error)
return false;
}
res = formatESSearchResult(res)
yield put({
type: 'saveData',
payload: res
})
},
*addCluster({payload}, {call, put, select}) {
let res = yield call(createClusterConfig, payload)
if(res.error){
message.error(res.error)
return false;
}
return res;
},
*updateCluster({payload}, {call, put, select}) {
let res = yield call(updateClusterConfig, payload)
if(res.error){
message.error(res.error)
return false;
}
return res;
},
*deleteCluster({payload}, {call, put, select}) {
let res = yield call(deleteClusterConfig, payload)
if(res.error){
message.error(res.error)
return false;
}
let {data, total} = yield select(state => state.clusterConfig);
data = data.filter((item)=>{
return item.id !== payload.id;
})
yield put({
type: 'saveData',
payload: {
data,
total: total -1,
}
})
return res;
}
},
reducers:{
saveData(state, {payload}){
return {
...state,
...payload,
}
}
}
}

View File

@ -0,0 +1,37 @@
import {pathPrefix, buildQueryArgs} from "./common";
import request from '@/utils/request';
export async function createClusterConfig(params) {
return request(`${pathPrefix}/system/cluster`, {
method: 'POST',
body: params,
});
}
export async function updateClusterConfig(params) {
return request(`${pathPrefix}/system/cluster/${params.id}`, {
method: 'PUT',
body: params,
});
}
export async function deleteClusterConfig(params) {
return request(`${pathPrefix}/system/cluster/${params.id}`, {
method: 'DELETE',
body: params,
});
}
export async function searchClusterConfig(params) {
let url = `${pathPrefix}/system/cluster`;
let args = buildQueryArgs({
name: params.name,
enabled: params.enabled
});
if(args.length > 0){
url += args;
}
return request(url, {
method: 'GET',
});
}

View File

@ -1 +1,15 @@
export const pathPrefix = '/_search-center'; export const pathPrefix = '/_search-center';
export function buildQueryArgs(params){
let argsStr = '';
for(let key in params){
if(typeof params[key] !== 'undefined') {
argsStr += `${key}=${params[key]}&`
}
}
if(argsStr.length > 0){
argsStr = '?' + argsStr
argsStr = argsStr.slice(0, argsStr.length -1)
}
return argsStr;
}