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

View File

@ -71,7 +71,7 @@ func main() {
}, func() {
orm.RegisterSchemaWithIndexName(model.Dict{}, "infini-dict")
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
import "time"
type ClusterConfig struct {
ID string `json:"id" elastic_meta:"_id"`
Name string `json:"name" elastic_mapping:"name:{type:text}"`
Endpoint string `json:"endpoint" elastic_mapping:"name:{type:text}"`
User string `json:"user" elastic_mapping:"name:{type:keyword}"`
Password string `json:"password"elastic_mapping:"name:{type:keyword}" `
Description string `json:"desc" elastic_mapping:"name:{type:text}"`
Enable bool `json:"enable" elastic_mapping:"name:{type:boolean}"`
Endpoint string `json:"endpoint" elastic_mapping:"endpoint:{type:text}"`
UserName string `json:"username" elastic_mapping:"username:{type:keyword}"`
Password string `json:"password" elastic_mapping:"password:{type:keyword}" `
Order int `json:"order" elastic_mapping:"order:{type:integer}"`
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',
},
{
path: '/system/cluster/new',
name: 'new-cluster',
path: '/system/cluster/edit',
name: 'edit-cluster',
component: './System/Cluster/Form',
hideInMenu: true
},

View File

@ -1,11 +1,22 @@
import React from 'react';
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()
@connect(({clusterConfig}) =>({
clusterConfig
}))
class ClusterForm extends React.Component{
state = {
confirmDirty: false,
}
componentDidMount() {
//console.log(this.props.clusterConfig.editMode)
}
compareToFirstPassword = (rule, value, callback) => {
const { form } = this.props;
if (value && value !== form.getFieldValue('password')) {
@ -22,6 +33,41 @@ class ClusterForm extends React.Component{
}
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() {
const {getFieldDecorator} = this.props.form;
const formItemLayout = {
@ -46,21 +92,24 @@ class ClusterForm extends React.Component{
},
},
};
const {editValue, editMode} = this.props.clusterConfig;
return (
<Card>
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Card title={editMode === 'NEW' ? '注册集群': '修改集群配置'}>
<Form {...formItemLayout}>
<Form.Item label="集群名称">
{getFieldDecorator('name', {
initialValue: editValue.name,
rules: [
{
required: true,
message: 'Please input cluster name!',
},
],
})(<Input />)}
})(<Input autoComplete='off' />)}
</Form.Item>
<Form.Item label="集群 URL">
{getFieldDecorator('endpoint', {
initialValue: editValue.endpoint,
rules: [
{
type: 'url', //https://github.com/yiminghe/async-validator#type
@ -75,12 +124,14 @@ class ClusterForm extends React.Component{
</Form.Item>
<Form.Item label="ES 用户名">
{getFieldDecorator('username', {
initialValue: editValue.username,
rules: [
],
})(<Input />)}
})(<Input autoComplete='off' />)}
</Form.Item>
<Form.Item label="ES 密码" hasFeedback>
{getFieldDecorator('password', {
initialValue: editValue.password,
rules: [
{
validator: this.validateToNextPassword,
@ -90,6 +141,7 @@ class ClusterForm extends React.Component{
</Form.Item>
<Form.Item label="ES 确认密码" hasFeedback>
{getFieldDecorator('confirm', {
initialValue: editValue.password,
rules: [
{
validator: this.compareToFirstPassword,
@ -99,25 +151,26 @@ class ClusterForm extends React.Component{
</Form.Item>
<Form.Item label="排序权重">
{getFieldDecorator('order', {
initialValue: 0
initialValue: editValue.order,
})(<InputNumber />)}
</Form.Item>
<Form.Item label="描述">
{getFieldDecorator('order', {
{getFieldDecorator('description', {
initialValue: editValue.description,
})(<Input.TextArea />)}
</Form.Item>
<Form.Item label="是否启用">
{getFieldDecorator('enabled', {
valuePropName: 'checked',
initialValue: true
initialValue: typeof editValue.enabled === 'undefined' ? true: editValue.enabled,
})(<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Register
<Button type="primary" onClick={this.handleSubmit}>
{editMode === 'NEW' ? 'Register': 'Save'}
</Button>
</Form.Item>
</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 {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 {connect} from "dva";
@Form.create()
@connect(({clusterConfig}) =>({
clusterConfig
}))
class Index extends React.Component {
columns = [{
title: '集群名称',
@ -32,8 +36,80 @@ class Index extends React.Component {
title: '是否启用',
dataIndex: '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() {
const {getFieldDecorator} = this.props.form;
const formItemLayout = {
@ -41,6 +117,7 @@ class Index extends React.Component {
wrapperCol: { span: 14 },
style: {marginBottom: 0}
};
const {data} = this.props.clusterConfig;
return (
<Card>
<Form>
@ -52,7 +129,7 @@ class Index extends React.Component {
</Col>
<Col md={8} sm={8}>
<div style={{paddingTop:4}}>
<Button type="primary" icon="search" onClick={this.handleSearch}>
<Button type="primary" icon="search" onClick={this.handleSearchClick}>
Search
</Button>
</div>
@ -66,15 +143,16 @@ class Index extends React.Component {
<span style={{marginRight:24}}><Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
onChange={this.handleEnabledChange}
defaultChecked
/></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>}
bordered={false}>
<Table
bordered
columns={this.columns}
dataSource={[]}
dataSource={data}
rowKey='id'
/>
</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;
}