fullfill document CURD preview

This commit is contained in:
silenceqi 2020-12-25 16:15:15 +08:00
parent 814405709f
commit a74511ad4d
15 changed files with 1540 additions and 907 deletions

View File

@ -0,0 +1,136 @@
package index_management
import (
"fmt"
"net/http"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/util"
)
type docReqBody struct {
Index string `json:"index"`
Action string `json:"action"`
Payload map[string]interface{} `json:"payload"`
PageIndex int `json:"pageIndex"`
PageSize int `json:"pageSize"`
Filter string `json:"filter"`
Cluster string `json:"cluster"`
}
func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch)
reqBody := docReqBody{}
resResult := map[string]interface{}{
"errno": "0",
"errmsg": "",
"payload": nil,
}
err := handler.DecodeJSON(req, &reqBody)
if err != nil {
panic(err)
}
indexName := ps.ByName("index")
var id string
if val, ok := reqBody.Payload["id"]; ok {
id = val.(string)
}
if _, ok := reqBody.Payload["_index"]; ok {
delete(reqBody.Payload, "_index")
}
switch reqBody.Action {
case "ADD":
id = util.GetUUID()
//security problem
_, err := client.Index(indexName, id, reqBody.Payload)
if err != nil {
panic(err)
}
reqBody.Payload["id"] = id
resResult["payload"] = reqBody.Payload
handler.WriteJSON(w, resResult, http.StatusOK)
case "SAVE":
if id == "" {
panic("empty id")
}
resp, err := client.Get(indexName, id)
if err != nil {
panic(err)
}
source := resp.Source
for k, v := range reqBody.Payload {
source[k] = v
}
_, err = client.Index(indexName, id, source)
if err != nil {
panic(err)
}
handler.WriteJSON(w, resResult, http.StatusOK)
case "DELETE":
if id == "" {
panic("empty id")
}
_, err = client.Delete(indexName, id)
if err != nil {
resResult["errmsg"] = err.Error()
resResult["errno"] = "E100003"
handler.WriteJSON(w, resResult, http.StatusOK)
return
}
handler.WriteJSON(w, resResult, http.StatusOK)
default:
var (
pageSize = 10
pageIndex = 1
)
if reqBody.PageSize > 0 {
pageSize = reqBody.PageSize
}
if reqBody.PageIndex > 0 {
pageIndex = reqBody.PageIndex
}
from := (pageIndex - 1) * pageSize
filter := `{"match_all": {}}`
if reqBody.Filter != "" {
filter = reqBody.Filter
}
query := fmt.Sprintf(`{"from":%d, "size": %d, "query": %s}`, from, pageSize, filter)
var reqBytes = []byte(query)
resp, err := client.SearchWithRawQueryDSL(indexName, reqBytes)
if err != nil {
panic(err)
}
result := formatESSearchResult(resp)
handler.WriteJSON(w, map[string]interface{}{
"errno": "0",
"errmsg": "",
"payload": result,
}, http.StatusOK)
}
}
func formatESSearchResult(esResp *elastic.SearchResponse) map[string]interface{} {
total := esResp.Hits.Total
if len(esResp.Hits.Hits) == 0 {
return map[string]interface{}{
"total": total,
"data": nil,
}
}
dataArr := make([]interface{}, 0, len(esResp.Hits.Hits))
for _, hit := range esResp.Hits.Hits {
if _, ok := hit.Source["id"]; !ok {
hit.Source["id"] = hit.ID
}
hit.Source["_index"] = hit.Index
dataArr = append(dataArr, hit.Source)
}
return map[string]interface{}{
"total": total,
"data": dataArr,
}
}

View File

@ -10,10 +10,12 @@ import (
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"infini.sh/search-center/config"
model2 "infini.sh/search-center/model"
)
type APIHandler struct {
Config *config.AppConfig
api.Handler
}
@ -44,37 +46,22 @@ func (handler APIHandler) GetDictListAction(w http.ResponseWriter, req *http.Req
func (handler APIHandler) CreateDictItemAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
//id := ps.ByName("id")
jq, err := handler.GetJSON(req)
if err != nil {
handler.Error(w, err)
return
}
name, err := jq.String("name")
if err != nil {
handler.Error(w, err)
return
}
tags, err := jq.ArrayOfStrings("tags")
if err != nil {
handler.Error(w, err)
return
}
content, err := jq.String("content")
if err != nil {
handler.Error(w, err)
return
}
createdAt := time.Now()
dict := model2.Dict{
ID: util.GetUUID(),
Name: name,
Tags: tags,
Content: []byte(content),
CreatedAt: createdAt,
UpdatedAt: createdAt,
}
err := handler.DecodeJSON(req, &dict)
if err != nil {
handler.WriteJSON(w, map[string]interface{}{
"payload": nil,
"errno": "E100001",
"errmsg": err.Error(),
}, http.StatusOK)
return
}
err = orm.Save(dict)
if err != nil {
@ -103,41 +90,18 @@ func (handler APIHandler) DeleteDictItemAction(w http.ResponseWriter, req *http.
}
func (handler APIHandler) UpdateDictItemAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
jq, err := handler.GetJSON(req)
dict := model2.Dict{}
err := handler.DecodeJSON(req, &dict)
if err != nil {
handler.Error(w, err)
handler.WriteJSON(w, map[string]interface{}{
"payload": nil,
"errno": "E100002",
"errmsg": err.Error(),
}, http.StatusOK)
return
}
id, err := jq.String("id")
if err != nil {
handler.Error(w, err)
return
}
name, err := jq.String("name")
if err != nil {
handler.Error(w, err)
return
}
tags, err := jq.ArrayOfStrings("tags")
if err != nil {
handler.Error(w, err)
return
}
content, err := jq.String("content")
if err != nil {
handler.Error(w, err)
return
}
updatedAt := time.Now()
dict := model2.Dict{
ID: id,
Name: name,
Tags: tags,
Content: []byte(content),
UpdatedAt: updatedAt,
}
dict.UpdatedAt = time.Now()
err = orm.Update(dict)
if err != nil {

View File

@ -4,10 +4,13 @@ import (
"infini.sh/framework/core/api"
"infini.sh/framework/core/ui"
"infini.sh/search-center/api/index_management"
"infini.sh/search-center/config"
)
func Init() {
handler := index_management.APIHandler{}
func Init(cfg *config.AppConfig) {
handler := index_management.APIHandler{
Config: cfg,
}
//ui.HandleUIMethod(api.POST, "/api/get_indices",index_management.API1)
ui.HandleUIMethod(api.GET, "/api/dict/_search", handler.GetDictListAction)
ui.HandleUIMethod(api.POST, "/api/dict/_create", handler.CreateDictItemAction)
@ -15,4 +18,5 @@ func Init() {
ui.HandleUIMethod(api.DELETE, "/api/dict/:id", handler.DeleteDictItemAction)
//ui.HandleUIMethod(api.DELETE, "/api/dict/", handler.DeleteDictItemAction2)
ui.HandleUIMethod(api.POST, "/api/dict/_update", handler.UpdateDictItemAction)
ui.HandleUIMethod(api.POST, "/api/doc/:index", handler.HandleDocumentAction)
}

View File

@ -2,7 +2,7 @@ package config
type AppConfig struct {
IndexName string `config:"index_name"`
ElasticConfig string `config:"elastic_config"`
Elasticsearch string `config:"elasticsearch"`
UILocalPath string `config:"ui_path"`
UILocalEnabled bool `config:"ui_local"`
UIVFSEnabled bool `config:"ui_vfs"`

View File

@ -47,7 +47,7 @@ func main() {
modules.Register()
appConfig = &config.AppConfig{
ElasticConfig: "default",
Elasticsearch: "default",
UILocalPath: ".public",
UIVFSEnabled: true,
UILocalEnabled: true,
@ -62,7 +62,7 @@ func main() {
}
//load web UI files
appUI = &UI{config: appConfig}
appUI = &UI{Config: appConfig}
appUI.InitUI()
//start each module, with enabled provider

View File

@ -2,7 +2,7 @@ elasticsearch:
- name: default
enabled: true
endpoint: http://localhost:9200
index_prefix: infini-
index_prefix:
basic_auth:
username: elastic
password: ZBdkVQUUdF1Sir4X4BGB

4
ui.go
View File

@ -15,7 +15,7 @@ import (
type UI struct {
api.Handler
config *config.AppConfig
Config *config.AppConfig
}
func (h UI) InitUI() {
@ -24,7 +24,7 @@ func (h UI) InitUI() {
ui.HandleUI("/", vfs.FileServer(vfs.VFS()))
uiapi.Init()
uiapi.Init(h.Config)
ui.HandleUIFunc("/api/", func(w http.ResponseWriter, req *http.Request) {
log.Warn("api: ", req.URL, " not implemented")

View File

@ -90,7 +90,7 @@ export default [
},
{
path: '/data/indices/doc',
component: './DataManagement/Document',
component: './DataManagement/Index',
},
{
path: '/data/indices/template',
@ -129,7 +129,7 @@ export default [
},{
path: '/data/doc',
name: 'query',
component: './DataManagement/Query',
component: './DataManagement/Document',
},
]
},

View File

@ -1,610 +1,392 @@
import React, { PureComponent, Fragment } from 'react';
import React, { Component } from 'react';
import { connect } from 'dva';
import {
Row,
Col,
Card,
Form,
Input,
Button,
Modal,
message,
Divider,
Drawer,
Tabs,
Descriptions,
Menu,
Dropdown,
Icon
} from 'antd';
import StandardTable from '@/components/StandardTable';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import { Col, Form, Row,Select, Input, Card,Icon, Table, InputNumber, Popconfirm,
Divider,Button,Tooltip, Cascader } from 'antd';
const {Option} = Select;
import styles from '../List/TableList.less';
import JSONPretty from 'react-json-prettify';
const EditableContext = React.createContext();
const FormItem = Form.Item;
const { TextArea } = Input;
const {TabPane} = Tabs;
class JSONWrapper extends PureComponent {
state ={
height: 400,
class EditableCell extends React.Component {
getInput = () => {
let {record, dataIndex} = this.props;
if (typeof record[dataIndex] === 'number') {
return <InputNumber />;
}
componentDidMount(){
let getElementTop = (elem)=>{
  var elemTop=elem.offsetTop;
  elem=elem.offsetParent;
  while(elem!=null){
    elemTop+=elem.offsetTop;
    elem=elem.offsetParent;
  }
  return elemTop;
}
console.log(getElementTop(this.refs.jsonw));
this.setState({height: window.innerHeight - getElementTop(this.refs.jsonw) -50});
}
render(){
return (
<div id="jsonw" ref="jsonw" onClick={()=>{console.log(document.getElementById('jsonw').offsetTop)}} style={{overflow:"scroll", height: this.state.height}}> {this.props.children}</div>
)
}
}
const CreateForm = Form.create()(props => {
const { modalVisible, form, handleAdd, handleModalVisible } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
handleAdd(fieldsValue);
});
};
return (
<Modal
destroyOnClose
title="新建索引"
visible={modalVisible}
width={640}
onOk={okHandle}
onCancel={() => handleModalVisible()}
>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引名称">
{form.getFieldDecorator('index', {
rules: [{ required: true, message: '请输入至少五个字符的名称!', min: 5 }],
})(<Input placeholder="请输入名称" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引设置">
{form.getFieldDecorator('settings', {
rules: [{ required: true }],
})(<TextArea
style={{ minHeight: 24 }}
placeholder="请输入"
rows={9}
/>)}
</FormItem>
</Modal>
);
});
const UpdateForm = Form.create()(props => {
const { updateModalVisible, handleUpdateModalVisible, handleUpdate,values,form } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
handleUpdate(fieldsValue);
});
return <Input />;
};
return (
<Modal
destroyOnClose
title="索引设置"
visible={updateModalVisible}
width={640}
onOk={okHandle}
onCancel={() => handleUpdateModalVisible()}
>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引名称">
{form.getFieldDecorator('index', {
initialValue: values.index,
rules: [{ required: true, message: '请输入至少五个字符的名称!', min: 5 }],
})(<Input placeholder="请输入名称" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引设置">
{form.getFieldDecorator('settings', {
initialValue: values.processors,
rules: [{ required: true }],
})(<TextArea
style={{ minHeight: 24 }}
placeholder="请输入"
rows={9}
/>)}
</FormItem>
</Modal>
);
});
/* eslint react/no-multi-comp:0 */
@connect(({ pipeline, loading }) => ({
pipeline,
loading: loading.models.pipeline,
}))
@Form.create()
class Document extends PureComponent {
state = {
modalVisible: false,
updateModalVisible: false,
expandForm: false,
selectedRows: [],
formValues: {},
updateFormValues: {},
drawerVisible: false,
editingIndex:{},
};
datasource = `[{"health":"green","status":"open","index":"blogs_fixed","uuid":"Q6zngGf9QVaWqpV0lF-0nw","pri":"1","rep":"1","docs.count":"1594","docs.deleted":"594","store.size":"17.9mb","pri.store.size":"8.9mb"},{"health":"red","status":"open","index":"elastic_qa","uuid":"_qkVlQ5LRoOKffV-nFj8Uw","pri":"1","rep":"1","docs.count":null,"docs.deleted":null,"store.size":null,"pri.store.size":null},{"health":"green","status":"open","index":".kibana-event-log-7.9.0-000001","uuid":"fgTtyl62Tc6F1ddJfPwqHA","pri":"1","rep":"1","docs.count":"20","docs.deleted":"0","store.size":"25kb","pri.store.size":"12.5kb"},{"health":"green","status":"open","index":"blogs","uuid":"Mb2n4wnNQSKqSToI_QO0Yg","pri":"1","rep":"1","docs.count":"1594","docs.deleted":"0","store.size":"11mb","pri.store.size":"5.5mb"},{"health":"green","status":"open","index":".kibana-event-log-7.9.0-000002","uuid":"8GpbwnDXR2KJUsw6srLnWw","pri":"1","rep":"1","docs.count":"9","docs.deleted":"0","store.size":"96.9kb","pri.store.size":"48.4kb"},{"health":"green","status":"open","index":".apm-agent-configuration","uuid":"vIaV9k2VS-W48oUOe2xNWA","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":"logs_server1","uuid":"u56jv2AyR2KOkruOfxIAnA","pri":"1","rep":"1","docs.count":"5386","docs.deleted":"0","store.size":"5.1mb","pri.store.size":"2.5mb"},{"health":"green","status":"open","index":".kibana_1","uuid":"dBCrfVblRPGVlYAIlP_Duw","pri":"1","rep":"1","docs.count":"3187","docs.deleted":"50","store.size":"24.8mb","pri.store.size":"12.4mb"},{"health":"green","status":"open","index":".tasks","uuid":"3RafayGeSNiqglO2BHof9Q","pri":"1","rep":"1","docs.count":"3","docs.deleted":"0","store.size":"39.9kb","pri.store.size":"19.9kb"},{"health":"green","status":"open","index":"filebeat-7.9.0-elastic_qa","uuid":"tktSYU14S3CrsrJb0ybpSQ","pri":"1","rep":"1","docs.count":"3009880","docs.deleted":"0","store.size":"1.6gb","pri.store.size":"850.1mb"},{"health":"green","status":"open","index":"analysis_test","uuid":"6ZHEAW1ST_qfg7mo4Bva4w","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":".apm-custom-link","uuid":"Y4N2TeVERrGacEGwY-NPAQ","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":"kibana_sample_data_ecommerce","uuid":"4FIWJKhGSr6bE72R0xEQyA","pri":"1","rep":"1","docs.count":"4675","docs.deleted":"0","store.size":"9.2mb","pri.store.size":"4.6mb"},{"health":"green","status":"open","index":".kibana_task_manager_1","uuid":"9afyndU_Q26oqOiEIoqRJw","pri":"1","rep":"1","docs.count":"6","docs.deleted":"2","store.size":"378.8kb","pri.store.size":"12.5kb"},{"health":"green","status":"open","index":".async-search","uuid":"2VbJgnN7SsqC-DWN64yXUQ","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"3.9kb","pri.store.size":"3.7kb"}]`;
columns = [
{
title: '索引名称',
dataIndex: 'index',
render: (text, record) => (
<a onClick={()=>{
this.setState({
editingIndex: record,
drawerVisible: true,
});
}}>{text}</a>
)
},
{
title: '文档数',
dataIndex: 'docs.count',
},
{
title: '主分片数',
dataIndex: 'pri'
},
{
title: '从分片数',
dataIndex: 'rep'
},
{
title: '存储大小',
dataIndex: 'store.size'
},
{
title: '操作',
render: (text, record) => (
<Fragment>
{/* <a onClick={() => this.handleUpdateModalVisible(true, record)}></a>
<Divider type="vertical" /> */}
<a onClick={() => {
this.state.selectedRows.push(record);
this.handleDeleteClick();
}}>删除</a>
<Divider type="vertical" />
<a onClick={() => {
this.state.selectedRows.push(record);
this.handleDeleteClick();
}}>文档管理</a>
</Fragment>
),
},
];
componentDidMount() {
const { dispatch } = this.props;
// dispatch({
// type: 'pipeline/fetch',
// });
}
handleStandardTableChange = (pagination, filtersArg, sorter) => {
const { dispatch } = this.props;
const { formValues } = this.state;
const filters = Object.keys(filtersArg).reduce((obj, key) => {
const newObj = { ...obj };
newObj[key] = getValue(filtersArg[key]);
return newObj;
}, {});
const params = {
currentPage: pagination.current,
pageSize: pagination.pageSize,
...formValues,
...filters,
};
if (sorter.field) {
params.sorter = `${sorter.field}_${sorter.order}`;
}
dispatch({
type: 'pipeline/fetch',
payload: params,
});
};
handleFormReset = () => {
const { form, dispatch } = this.props;
form.resetFields();
this.setState({
formValues: {},
});
dispatch({
type: 'pipeline/fetch',
payload: {},
});
};
handleDeleteClick = e => {
const { dispatch } = this.props;
const { selectedRows } = this.state;
if (!selectedRows) return;
dispatch({
type: 'pipeline/delete',
payload: {
key: selectedRows.map(row => row.name),
},
callback: () => {
this.setState({
selectedRows: [],
});
},
});
};
handleSelectRows = rows => {
this.setState({
selectedRows: rows,
});
};
handleSearch = e => {
e.preventDefault();
const { dispatch, form } = this.props;
form.validateFields((err, fieldsValue) => {
if (err) return;
const values = {
...fieldsValue,
updatedAt: fieldsValue.updatedAt && fieldsValue.updatedAt.valueOf(),
};
this.setState({
formValues: values,
});
dispatch({
type: 'rule/fetch',
payload: values,
});
});
};
handleModalVisible = flag => {
this.setState({
modalVisible: !!flag,
});
};
handleUpdateModalVisible = (flag, record) => {
this.setState({
updateModalVisible: !!flag,
updateFormValues: record || {},
});
};
handleAdd = fields => {
const { dispatch } = this.props;
dispatch({
type: 'pipeline/add',
payload: {
name: fields.name,
desc: fields.desc,
processors: fields.processors,
},
});
message.success('添加成功');
this.handleModalVisible();
};
handleUpdate = fields => {
const { dispatch } = this.props;
dispatch({
type: 'pipeline/update',
payload: {
name: fields.name,
desc: fields.desc,
processors: fields.processors,
},
});
message.success('修改成功');
this.handleUpdateModalVisible();
};
renderSimpleForm() {
renderCell = ({ getFieldDecorator }) => {
const {
form: { getFieldDecorator },
editing,
dataIndex,
title,
record,
index,
children,
...restProps
} = this.props;
return (
<Form onSubmit={this.handleSearch} layout="inline">
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}>
<FormItem label="索引名称">
{getFieldDecorator('name')(<Input placeholder="请输入" />)}
</FormItem>
</Col>
<Col md={8} sm={24}>
<span className={styles.submitButtons}>
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
重置
</Button>
</span>
</Col>
</Row>
</Form>
<td {...restProps}>
{editing ? (
<Form.Item style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`,
},
],
initialValue: record[dataIndex],
})(this.getInput())}
</Form.Item>
) : (
children
)}
</td>
);
};
render() {
return <EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>;
}
}
renderForm() {
return this.renderSimpleForm();
@Form.create()
class EditableTable extends React.Component {
constructor(props) {
super(props);
this.operField = {
title: 'operation',
dataIndex: 'operation',
render: (text, record) => {
const { editingKey } = this.props.doclist;
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{form => (
<a
onClick={() => this.save(form, record.id)}
style={{ marginRight: 8 }}
>
Save
</a>
)}
</EditableContext.Consumer>
<Popconfirm title="Sure to cancel?" onConfirm={() => this.cancel(record.id)}>
<a>Cancel</a>
</Popconfirm>
</span>
) : (<div>
<a disabled={editingKey !== ''} onClick={() => this.edit(record)}>
Edit
</a>
<Divider type="vertical"/>
<Popconfirm title="Sure to delete?" onConfirm={() => this.delete(record)}>
<a>Delete</a>
</Popconfirm>
</div>
);
},
};
}
isEditing = record => record.id === this.props.doclist.editingKey;
cancel = () => {
const {dispatch, doclist} = this.props;
if(!doclist.isAddNew){
dispatch({
type: 'document/saveData',
payload: { editingKey: '' },
});
}else{
dispatch({
type: 'document/cancelNew',
payload: {},
});
}
};
save(form, key) {
const {dispatch,doclist} = this.props;
form.validateFields((error, row) => {
if (error) {
return;
}
//console.log(row, key, doclist._index);
if(!doclist.isAddNew){
dispatch({
type: 'document/saveDocItem',
payload: {
index: doclist._index,
data: {
id: key,
...row,
}
}
})
}else{
dispatch({
type: 'document/addDocItem',
payload: {
index: doclist.index,
data: row,
}
})
}
});
}
edit(record) {
const {dispatch} = this.props;
dispatch({
type: 'document/saveData',
payload: { editingKey: record.id, _index: record._index }
});
}
delete(record) {
const {dispatch} = this.props;
dispatch({
type: 'document/deleteDocItem',
payload: {
index: record._index,
data: {
id: record.id,
}
}
});
}
handlePageChange = (pageIndex)=>{
const {fetchData, doclist} = this.props;
fetchData({
pageIndex: pageIndex,
pageSize: doclist.pageSize,
index: doclist.index,
cluster: doclist.cluster,
filter: doclist.filter,
})
}
onShowSizeChange(current, pageSize) {
console.log(current, pageSize);
}
render() {
const data = {
list: JSON.parse(this.datasource),
pagination: {
pageSize: 5,
let {doclist} = this.props;
let columns = [];
if(doclist.data && doclist.data.length > 0 ){
for(let key in doclist.data[0]){
if(["_index"].includes(key)){
continue;
}
let col = {
title: key,
dataIndex: key,
ellipsis: true,
render: (text)=>(<Tooltip placement="top" title={text}>{text}</Tooltip>),
onCell: record => ({
record,
dataIndex: key,
title: key,
editing: this.isEditing(record),
// onMouseEnter: event => {console.log(event)},
// onMouseLeave: event => {},
}),
}
if(["id"].includes(key)){
col.onCell = "";
}
columns.push(col)
}
}
columns.push(this.operField);
//console.log(columns);
const components = {
body: {
cell: EditableCell,
},
};
const { selectedRows, modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state;
const parentMethods = {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
};
const updateMethods = {
handleUpdateModalVisible: this.handleUpdateModalVisible,
handleUpdate: this.handleUpdate,
};
return (
<Fragment>
<Card bordered={false}>
<div className={styles.tableList}>
<div className={styles.tableListForm}>{this.renderForm()}</div>
<div className={styles.tableListOperator}>
<Button icon="plus" type="primary" onClick={() => this.handleModalVisible(true)}>
新建
</Button>
{selectedRows.length > 0 && (
<span>
<Button onClick={() => this.handleDeleteClick()}>删除</Button>
</span>
)}
</div>
<StandardTable
selectedRows={selectedRows}
data={data}
columns={this.columns}
onSelectRow={this.handleSelectRows}
onChange={this.handleStandardTableChange}
/>
</div>
</Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} />
{updateFormValues && Object.keys(updateFormValues).length ? (
<UpdateForm
{...updateMethods}
updateModalVisible={updateModalVisible}
values={updateFormValues}
/>
) : null}
<Drawer title={editingIndex.index}
visible={drawerVisible}
onClose={()=>{
this.setState({
drawerVisible: false,
});
<EditableContext.Provider value={this.props.form}>
<Table
components={components}
bordered
rowKey="id"
size="small"
loading={doclist.isLoading}
dataSource={doclist.data}
columns={columns}
rowClassName="editable-row"
pagination={{
onChange: this.cancel,
//showSizeChanger: true,
//onShowSizeChange: this.onShowSizeChange,
total: doclist.total? doclist.total.value: 0,
pageSize: doclist.pageSize,
current: doclist.pageIndex,
onChange: this.handlePageChange,
showTotal: (total, range) => `Total ${total} items`,
size: 'small',
}}
width={640}
>
<Tabs defaultActiveKey="1" onChange={()=>{}}>
<TabPane tab="Summary" key="1">
<Descriptions title="General" column={2}>
<Descriptions.Item label="Health">green</Descriptions.Item>
<Descriptions.Item label="Status">open</Descriptions.Item>
<Descriptions.Item label="Primaries">1</Descriptions.Item>
<Descriptions.Item label="Replicas">0</Descriptions.Item>
<Descriptions.Item label="Docs Count">5</Descriptions.Item>
<Descriptions.Item label="Docs Deleted">0</Descriptions.Item>
<Descriptions.Item label="Storage Size">115.3kb</Descriptions.Item>
<Descriptions.Item label="Primary Storage Size"></Descriptions.Item>
<Descriptions.Item label="Alias">
</Descriptions.Item>
</Descriptions>
</TabPane>
<TabPane tab="Mappings" key="2">
<JSONWrapper>
<JSONPretty json={JSON.parse(`{
"mappings": {
"_doc": {
"dynamic": "strict",
"_meta": {
"migrationMappingPropertyHashes": {
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
"originId": "2f4316de49999235636386fe51dc06c1",
"task": "235412e52d09e7165fac8a67a43ad6b4",
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
"references": "7997cf5a56cc02bdc9c93361bde732b0",
"namespace": "2f4316de49999235636386fe51dc06c1",
"type": "2f4316de49999235636386fe51dc06c1",
"namespaces": "2f4316de49999235636386fe51dc06c1"
}
},
"properties": {
"migrationVersion": {
"dynamic": "true",
"properties": {
"task": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"namespace": {
"type": "keyword"
},
"namespaces": {
"type": "keyword"
},
"originId": {
"type": "keyword"
},
"references": {
"type": "nested",
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
}
},
"task": {
"properties": {
"attempts": {
"type": "integer"
},
"ownerId": {
"type": "keyword"
},
"params": {
"type": "text"
},
"retryAt": {
"type": "date"
},
"runAt": {
"type": "date"
},
"schedule": {
"properties": {
"interval": {
"type": "keyword"
}
}
},
"scheduledAt": {
"type": "date"
},
"scope": {
"type": "keyword"
},
"startedAt": {
"type": "date"
},
"state": {
"type": "text"
},
"status": {
"type": "keyword"
},
"taskType": {
"type": "keyword"
},
"user": {
"type": "keyword"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
}
}
}
`)} theme={{
background: '#F5F7FA',
brace: '#343741',
keyQuotes: '#343741',
valueQuotes: '#343741',
colon: '#343741',
comma: '#343741',
key: '#343741',
value: {
string: '#343741',
null: '#343741',
number: '#343741',
boolean: '#343741',
},
bracket: '#343741',
}} /></JSONWrapper>
</TabPane>
<TabPane tab="Stats" key="3">
Content of Tab Pane 3
</TabPane>
<TabPane tab="Edit settings" key="4">
Content of Tab Pane 3
</TabPane>
</Tabs>
<div style={{position:'absolute', bottom: 10}}>
<Dropdown
placement="topLeft"
overlay={(
<Menu onClick={()=>{}}>
<Menu.Item key="1">
<Icon type="delete" />
Delete
</Menu.Item>
<Menu.Item key="2">
<Icon type="edit" />
Edit
</Menu.Item>
<Menu.Item key="3">
<Icon type="close" />
Close
</Menu.Item>
</Menu>
)}>
<Button type="primary">
Manage <Icon type="up" />
</Button>
</Dropdown>
</div>
</Drawer>
</Fragment>
/>
</EditableContext.Provider>
);
}
}
@connect(({document})=>({
document
}))
@Form.create()
class Doucment extends React.Component {
state={
bodyDisplay: 'none',
}
fetchData = (params) => {
const {dispatch} = this.props;
dispatch({
type: 'document/fetchDocList',
payload: params,
})
}
componentDidMount(){
this.fetchData({
pageSize: 10,
pageIndex: 1,
index: 'infini-test',
})
}
handleNewClick = ()=>{
const {dispatch, document} = this.props;
if(!document.data || document.data.length == 0 || document.isAddNew){
return;
}
let keys = Object.keys(document.data[0])
let newDoc = {};
for(let key of keys){
newDoc[key] = ""
}
dispatch({
type: 'document/_addNew',
payload: {
docItem: newDoc,
extra: {
isAddNew: true
}
},
})
}
handleSearchClick = (value)=>{
const [cluster, index] = this.indexEl.state.value;
let targetIndex = index;
if(value != ""){
targetIndex = value;
}
console.log(targetIndex);
this.fetchData({
cluster,
index: targetIndex,
pageSize: 10,
pageIndex: 1,
filter: this.filterEl.state.value,
})
}
renderNew = ()=>{
const {indices} = this.props.document;
if((indices && indices.length > 1)){
return;
}
return (
<div>
{(indices && indices.length > 1) ? (<Select style={{width: 200, marginRight:5}} placeholder="please select a index">
{indices.map(item=>{
return (<Select.Option key={item} label={item}>{item}</Select.Option>)
})}
</Select>) : ''}
<Button type="primary" icon="plus" onClick={this.handleNewClick}>新建</Button>
</div>
)
}
render(){
// const {getFieldDecorator} = this.props.form;
//console.log(this.props.document);
const options =[
{
value: 'single-es',
label: 'single-es',
children: [
{
value: 'infini-test',
label: 'infini-test',
}
]}];
return (
<div>
<Card>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={20}>
<Input.Group compact>
<Cascader
options={options}
ref={el=>{this.indexEl=el}}
style={{width: '20%'}}
onChange={(value, selectedOptions)=>{console.log(value)}}
placeholder="Please select index"
showSearch={{filter: (inputValue, path)=>path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1) }}
/>
<Input.Search
style={{width:"80%"}}
placeholder="input rewrite index or index pattern"
enterButton="execute"
onSearch={this.handleSearchClick}
/>
</Input.Group>
</Col>
<Col span={4}>
<a style={{marginTop:5,display:'block'}} onClick={(e)=>{
this.setState((preState)=>{
if(preState.bodyDisplay == 'none') {
return {
bodyDisplay: 'block',
};
}else{
return {
bodyDisplay: 'none'
};
}
});
}}>{this.state.bodyDisplay == 'none' ? '高级':'收起'}<Icon type="down" /></a>
</Col>
</Row>
<Row style={{display: this.state.bodyDisplay}} gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={20}>
<Input.TextArea ref={el=>{this.filterEl=el}} placeholder="input query filter (elasticsearch query DSL)" rows={8}/>
</Col>
</Row>
</Card>
<div>
<Card title={`Index: ${this.props.document.index}`}
bodyStyle={{padding:0, paddingBottom: 24}}
extra={this.renderNew()}
bordered={false}>
<EditableTable doclist={this.props.document} dispatch={this.props.dispatch}
fetchData={(params)=>{this.fetchData(params)}}/>
</Card>
</div>
</div>
)
}
}
export default Document;
export default Doucment;

View File

@ -0,0 +1,610 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import {
Row,
Col,
Card,
Form,
Input,
Button,
Modal,
message,
Divider,
Drawer,
Tabs,
Descriptions,
Menu,
Dropdown,
Icon
} from 'antd';
import StandardTable from '@/components/StandardTable';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from '../List/TableList.less';
import JSONPretty from 'react-json-prettify';
const FormItem = Form.Item;
const { TextArea } = Input;
const {TabPane} = Tabs;
class JSONWrapper extends PureComponent {
state ={
height: 400,
}
componentDidMount(){
let getElementTop = (elem)=>{
  var elemTop=elem.offsetTop;
  elem=elem.offsetParent;
  while(elem!=null){
    elemTop+=elem.offsetTop;
    elem=elem.offsetParent;
  }
  return elemTop;
}
console.log(getElementTop(this.refs.jsonw));
this.setState({height: window.innerHeight - getElementTop(this.refs.jsonw) -50});
}
render(){
return (
<div id="jsonw" ref="jsonw" onClick={()=>{console.log(document.getElementById('jsonw').offsetTop)}} style={{overflow:"scroll", height: this.state.height}}> {this.props.children}</div>
)
}
}
const CreateForm = Form.create()(props => {
const { modalVisible, form, handleAdd, handleModalVisible } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
handleAdd(fieldsValue);
});
};
return (
<Modal
destroyOnClose
title="新建索引"
visible={modalVisible}
width={640}
onOk={okHandle}
onCancel={() => handleModalVisible()}
>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引名称">
{form.getFieldDecorator('index', {
rules: [{ required: true, message: '请输入至少五个字符的名称!', min: 5 }],
})(<Input placeholder="请输入名称" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引设置">
{form.getFieldDecorator('settings', {
rules: [{ required: true }],
})(<TextArea
style={{ minHeight: 24 }}
placeholder="请输入"
rows={9}
/>)}
</FormItem>
</Modal>
);
});
const UpdateForm = Form.create()(props => {
const { updateModalVisible, handleUpdateModalVisible, handleUpdate,values,form } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
handleUpdate(fieldsValue);
});
};
return (
<Modal
destroyOnClose
title="索引设置"
visible={updateModalVisible}
width={640}
onOk={okHandle}
onCancel={() => handleUpdateModalVisible()}
>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引名称">
{form.getFieldDecorator('index', {
initialValue: values.index,
rules: [{ required: true, message: '请输入至少五个字符的名称!', min: 5 }],
})(<Input placeholder="请输入名称" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="索引设置">
{form.getFieldDecorator('settings', {
initialValue: values.processors,
rules: [{ required: true }],
})(<TextArea
style={{ minHeight: 24 }}
placeholder="请输入"
rows={9}
/>)}
</FormItem>
</Modal>
);
});
/* eslint react/no-multi-comp:0 */
@connect(({ pipeline, loading }) => ({
pipeline,
loading: loading.models.pipeline,
}))
@Form.create()
class Index extends PureComponent {
state = {
modalVisible: false,
updateModalVisible: false,
expandForm: false,
selectedRows: [],
formValues: {},
updateFormValues: {},
drawerVisible: false,
editingIndex:{},
};
datasource = `[{"health":"green","status":"open","index":"blogs_fixed","uuid":"Q6zngGf9QVaWqpV0lF-0nw","pri":"1","rep":"1","docs.count":"1594","docs.deleted":"594","store.size":"17.9mb","pri.store.size":"8.9mb"},{"health":"red","status":"open","index":"elastic_qa","uuid":"_qkVlQ5LRoOKffV-nFj8Uw","pri":"1","rep":"1","docs.count":null,"docs.deleted":null,"store.size":null,"pri.store.size":null},{"health":"green","status":"open","index":".kibana-event-log-7.9.0-000001","uuid":"fgTtyl62Tc6F1ddJfPwqHA","pri":"1","rep":"1","docs.count":"20","docs.deleted":"0","store.size":"25kb","pri.store.size":"12.5kb"},{"health":"green","status":"open","index":"blogs","uuid":"Mb2n4wnNQSKqSToI_QO0Yg","pri":"1","rep":"1","docs.count":"1594","docs.deleted":"0","store.size":"11mb","pri.store.size":"5.5mb"},{"health":"green","status":"open","index":".kibana-event-log-7.9.0-000002","uuid":"8GpbwnDXR2KJUsw6srLnWw","pri":"1","rep":"1","docs.count":"9","docs.deleted":"0","store.size":"96.9kb","pri.store.size":"48.4kb"},{"health":"green","status":"open","index":".apm-agent-configuration","uuid":"vIaV9k2VS-W48oUOe2xNWA","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":"logs_server1","uuid":"u56jv2AyR2KOkruOfxIAnA","pri":"1","rep":"1","docs.count":"5386","docs.deleted":"0","store.size":"5.1mb","pri.store.size":"2.5mb"},{"health":"green","status":"open","index":".kibana_1","uuid":"dBCrfVblRPGVlYAIlP_Duw","pri":"1","rep":"1","docs.count":"3187","docs.deleted":"50","store.size":"24.8mb","pri.store.size":"12.4mb"},{"health":"green","status":"open","index":".tasks","uuid":"3RafayGeSNiqglO2BHof9Q","pri":"1","rep":"1","docs.count":"3","docs.deleted":"0","store.size":"39.9kb","pri.store.size":"19.9kb"},{"health":"green","status":"open","index":"filebeat-7.9.0-elastic_qa","uuid":"tktSYU14S3CrsrJb0ybpSQ","pri":"1","rep":"1","docs.count":"3009880","docs.deleted":"0","store.size":"1.6gb","pri.store.size":"850.1mb"},{"health":"green","status":"open","index":"analysis_test","uuid":"6ZHEAW1ST_qfg7mo4Bva4w","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":".apm-custom-link","uuid":"Y4N2TeVERrGacEGwY-NPAQ","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"416b","pri.store.size":"208b"},{"health":"green","status":"open","index":"kibana_sample_data_ecommerce","uuid":"4FIWJKhGSr6bE72R0xEQyA","pri":"1","rep":"1","docs.count":"4675","docs.deleted":"0","store.size":"9.2mb","pri.store.size":"4.6mb"},{"health":"green","status":"open","index":".kibana_task_manager_1","uuid":"9afyndU_Q26oqOiEIoqRJw","pri":"1","rep":"1","docs.count":"6","docs.deleted":"2","store.size":"378.8kb","pri.store.size":"12.5kb"},{"health":"green","status":"open","index":".async-search","uuid":"2VbJgnN7SsqC-DWN64yXUQ","pri":"1","rep":"1","docs.count":"0","docs.deleted":"0","store.size":"3.9kb","pri.store.size":"3.7kb"}]`;
columns = [
{
title: '索引名称',
dataIndex: 'index',
render: (text, record) => (
<a onClick={()=>{
this.setState({
editingIndex: record,
drawerVisible: true,
});
}}>{text}</a>
)
},
{
title: '文档数',
dataIndex: 'docs.count',
},
{
title: '主分片数',
dataIndex: 'pri'
},
{
title: '从分片数',
dataIndex: 'rep'
},
{
title: '存储大小',
dataIndex: 'store.size'
},
{
title: '操作',
render: (text, record) => (
<Fragment>
{/* <a onClick={() => this.handleUpdateModalVisible(true, record)}></a>
<Divider type="vertical" /> */}
<a onClick={() => {
this.state.selectedRows.push(record);
this.handleDeleteClick();
}}>删除</a>
<Divider type="vertical" />
<a onClick={() => {
this.state.selectedRows.push(record);
this.handleDeleteClick();
}}>文档管理</a>
</Fragment>
),
},
];
componentDidMount() {
const { dispatch } = this.props;
// dispatch({
// type: 'pipeline/fetch',
// });
}
handleStandardTableChange = (pagination, filtersArg, sorter) => {
const { dispatch } = this.props;
const { formValues } = this.state;
const filters = Object.keys(filtersArg).reduce((obj, key) => {
const newObj = { ...obj };
newObj[key] = getValue(filtersArg[key]);
return newObj;
}, {});
const params = {
currentPage: pagination.current,
pageSize: pagination.pageSize,
...formValues,
...filters,
};
if (sorter.field) {
params.sorter = `${sorter.field}_${sorter.order}`;
}
dispatch({
type: 'pipeline/fetch',
payload: params,
});
};
handleFormReset = () => {
const { form, dispatch } = this.props;
form.resetFields();
this.setState({
formValues: {},
});
dispatch({
type: 'pipeline/fetch',
payload: {},
});
};
handleDeleteClick = e => {
const { dispatch } = this.props;
const { selectedRows } = this.state;
if (!selectedRows) return;
dispatch({
type: 'pipeline/delete',
payload: {
key: selectedRows.map(row => row.name),
},
callback: () => {
this.setState({
selectedRows: [],
});
},
});
};
handleSelectRows = rows => {
this.setState({
selectedRows: rows,
});
};
handleSearch = e => {
e.preventDefault();
const { dispatch, form } = this.props;
form.validateFields((err, fieldsValue) => {
if (err) return;
const values = {
...fieldsValue,
updatedAt: fieldsValue.updatedAt && fieldsValue.updatedAt.valueOf(),
};
this.setState({
formValues: values,
});
dispatch({
type: 'rule/fetch',
payload: values,
});
});
};
handleModalVisible = flag => {
this.setState({
modalVisible: !!flag,
});
};
handleUpdateModalVisible = (flag, record) => {
this.setState({
updateModalVisible: !!flag,
updateFormValues: record || {},
});
};
handleAdd = fields => {
const { dispatch } = this.props;
dispatch({
type: 'pipeline/add',
payload: {
name: fields.name,
desc: fields.desc,
processors: fields.processors,
},
});
message.success('添加成功');
this.handleModalVisible();
};
handleUpdate = fields => {
const { dispatch } = this.props;
dispatch({
type: 'pipeline/update',
payload: {
name: fields.name,
desc: fields.desc,
processors: fields.processors,
},
});
message.success('修改成功');
this.handleUpdateModalVisible();
};
renderSimpleForm() {
const {
form: { getFieldDecorator },
} = this.props;
return (
<Form onSubmit={this.handleSearch} layout="inline">
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}>
<FormItem label="索引名称">
{getFieldDecorator('name')(<Input placeholder="请输入" />)}
</FormItem>
</Col>
<Col md={8} sm={24}>
<span className={styles.submitButtons}>
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
重置
</Button>
</span>
</Col>
</Row>
</Form>
);
}
renderForm() {
return this.renderSimpleForm();
}
render() {
const data = {
list: JSON.parse(this.datasource),
pagination: {
pageSize: 5,
},
};
const { selectedRows, modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state;
const parentMethods = {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
};
const updateMethods = {
handleUpdateModalVisible: this.handleUpdateModalVisible,
handleUpdate: this.handleUpdate,
};
return (
<Fragment>
<Card bordered={false}>
<div className={styles.tableList}>
<div className={styles.tableListForm}>{this.renderForm()}</div>
<div className={styles.tableListOperator}>
<Button icon="plus" type="primary" onClick={() => this.handleModalVisible(true)}>
新建
</Button>
{selectedRows.length > 0 && (
<span>
<Button onClick={() => this.handleDeleteClick()}>删除</Button>
</span>
)}
</div>
<StandardTable
selectedRows={selectedRows}
data={data}
columns={this.columns}
onSelectRow={this.handleSelectRows}
onChange={this.handleStandardTableChange}
/>
</div>
</Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} />
{updateFormValues && Object.keys(updateFormValues).length ? (
<UpdateForm
{...updateMethods}
updateModalVisible={updateModalVisible}
values={updateFormValues}
/>
) : null}
<Drawer title={editingIndex.index}
visible={drawerVisible}
onClose={()=>{
this.setState({
drawerVisible: false,
});
}}
width={640}
>
<Tabs defaultActiveKey="1" onChange={()=>{}}>
<TabPane tab="Summary" key="1">
<Descriptions title="General" column={2}>
<Descriptions.Item label="Health">green</Descriptions.Item>
<Descriptions.Item label="Status">open</Descriptions.Item>
<Descriptions.Item label="Primaries">1</Descriptions.Item>
<Descriptions.Item label="Replicas">0</Descriptions.Item>
<Descriptions.Item label="Docs Count">5</Descriptions.Item>
<Descriptions.Item label="Docs Deleted">0</Descriptions.Item>
<Descriptions.Item label="Storage Size">115.3kb</Descriptions.Item>
<Descriptions.Item label="Primary Storage Size"></Descriptions.Item>
<Descriptions.Item label="Alias">
</Descriptions.Item>
</Descriptions>
</TabPane>
<TabPane tab="Mappings" key="2">
<JSONWrapper>
<JSONPretty json={JSON.parse(`{
"mappings": {
"_doc": {
"dynamic": "strict",
"_meta": {
"migrationMappingPropertyHashes": {
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
"originId": "2f4316de49999235636386fe51dc06c1",
"task": "235412e52d09e7165fac8a67a43ad6b4",
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
"references": "7997cf5a56cc02bdc9c93361bde732b0",
"namespace": "2f4316de49999235636386fe51dc06c1",
"type": "2f4316de49999235636386fe51dc06c1",
"namespaces": "2f4316de49999235636386fe51dc06c1"
}
},
"properties": {
"migrationVersion": {
"dynamic": "true",
"properties": {
"task": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"namespace": {
"type": "keyword"
},
"namespaces": {
"type": "keyword"
},
"originId": {
"type": "keyword"
},
"references": {
"type": "nested",
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
}
},
"task": {
"properties": {
"attempts": {
"type": "integer"
},
"ownerId": {
"type": "keyword"
},
"params": {
"type": "text"
},
"retryAt": {
"type": "date"
},
"runAt": {
"type": "date"
},
"schedule": {
"properties": {
"interval": {
"type": "keyword"
}
}
},
"scheduledAt": {
"type": "date"
},
"scope": {
"type": "keyword"
},
"startedAt": {
"type": "date"
},
"state": {
"type": "text"
},
"status": {
"type": "keyword"
},
"taskType": {
"type": "keyword"
},
"user": {
"type": "keyword"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
}
}
}
`)} theme={{
background: '#F5F7FA',
brace: '#343741',
keyQuotes: '#343741',
valueQuotes: '#343741',
colon: '#343741',
comma: '#343741',
key: '#343741',
value: {
string: '#343741',
null: '#343741',
number: '#343741',
boolean: '#343741',
},
bracket: '#343741',
}} /></JSONWrapper>
</TabPane>
<TabPane tab="Stats" key="3">
Content of Tab Pane 3
</TabPane>
<TabPane tab="Edit settings" key="4">
Content of Tab Pane 3
</TabPane>
</Tabs>
<div style={{position:'absolute', bottom: 10}}>
<Dropdown
placement="topLeft"
overlay={(
<Menu onClick={()=>{}}>
<Menu.Item key="1">
<Icon type="delete" />
Delete
</Menu.Item>
<Menu.Item key="2">
<Icon type="edit" />
Edit
</Menu.Item>
<Menu.Item key="3">
<Icon type="close" />
Close
</Menu.Item>
</Menu>
)}>
<Button type="primary">
Manage <Icon type="up" />
</Button>
</Dropdown>
</div>
</Drawer>
</Fragment>
);
}
}
export default Index;

View File

@ -1,141 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import { Col, Form, Row,Select, Input, Card,Icon,List, Descriptions } from 'antd';
const {Option} = Select;
@Form.create()
class Query extends React.Component {
state={
bodyDisplay: 'none',
data: [{
index: 'blogs-001',
id: 'dpOsA3YBCjFOm54VZoNF',
source: `{
"title" : "elastic search test title",
"content": "如默认结构不满足需求,可以自定义该模板,但是自定义模板时必须包含各个 dom 节点的 class样式可以自定义。",
"created_at" : "2020-11-23"
}`
},
{
index: 'blogs-002',
id: "dpOsA3YBCjFOm54VZoNB",
source: `{
"title" : "elastic search test title",
"created_at" : "2020-11-23",
"content": "如默认结构不满足需求,可以自定义该模板,但是自定义模板时必须包含各个 dom 节点的 class样式可以自定义。"
}`
},
{
index: 'blogs-002',
id: "dpOsA3YBCjFOm54VZoNC",
source: `{
"title" : "elastic search test title",
"created_at" : "2020-11-23",
"content":"如默认结构不满足需求,可以自定义该模板,但是自定义模板时必须包含各个 dom 节点的 class样式可以自定义。"
}`
},
{
index: 'blogs-001',
id:"dpOsA3YBCjFOm54VZoNG",
source: `{
"title" : "elastic search test title",
"content":"如默认结构不满足需求,可以自定义该模板,但是自定义模板时必须包含各个 dom 节点的 class样式可以自定义。",
"created_at" : "2020-11-23"
}`
}, {
index: 'blogs-001',
id:"dpOsA3YBCjFOm54VZoNG",
source: `{
"title" : "elastic search test title",
"content":"如默认结构不满足需求,可以自定义该模板,但是自定义模板时必须包含各个 dom 节点的 class样式可以自定义。",
"created_at" : "2020-11-23"
}`
}]
}
render(){
// const {getFieldDecorator} = this.props.form;
return (
<div>
<Card>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={20}>
<Input.Group compact>
<Select
defaultValue="GET"
style={{ width: 80 }}
>
<Option value="GET">GET</Option>
<Option value="POST">POST</Option>
<Option value="PUT">PUT</Option>
</Select>
<Input.Search
style={{width:"80%"}}
placeholder="input query url"
enterButton="execute"
onSearch={value => console.log(value)}
/>
</Input.Group>
</Col>
<Col span={4}>
<a style={{marginTop:5,display:'block'}} onClick={(e)=>{
this.setState((preState)=>{
if(preState.bodyDisplay == 'none') {
return {
bodyDisplay: 'block',
};
}else{
return {
bodyDisplay: 'none'
};
}
});
}}>{this.state.bodyDisplay == 'none' ? '展开':'收起'}<Icon type="down" /></a>
</Col>
</Row>
<Row style={{display: this.state.bodyDisplay}} gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={20}>
<Input.TextArea placeholder="query body" rows={8}/>
</Col>
</Row>
<List
grid={{ gutter: 16, column: 2}}
dataSource={this.state.data}
pagination={{
onChange: page => {
console.log(page);
},
pageSize: 4,
total: 50
}}
renderItem={item => (
<List.Item>
<Card title={`${item.index} ${item.id}`}
extra={<a onClick={()=>{}}>More</a>}
actions={[
<Icon type="edit" key="edit" onClick={(e)=>{}} />,
<Icon type="delete" key="delete" />,
]}>
<Descriptions bordered column={1}>
{/* <Descriptions.Item label="ID">
{item.id}
</Descriptions.Item> */}
<Descriptions.Item label="Source">
{item.source}
</Descriptions.Item>
</Descriptions>
</Card>
</List.Item>
)}
/>
</Card>
</div>
)
}
}
export default Query;

View File

@ -0,0 +1,208 @@
import {getDocList, saveDoc, deleteDoc, addDoc} from '@/services/doc';
import { message } from 'antd';
function encodeObjectField(doc){
//let rawData = {};
for(let key of Object.keys(doc)){
if(typeof doc[key] == 'object'){
// let docId = doc['id'];
// !rawData[docId] && (rawData[docId] = {});
// rawData[docId][key] = doc[key];
doc[key] = JSON.stringify(doc[key]);
}
}
return doc;
}
function decodeObjectField(doc){
for(let key of Object.keys(doc)){
if(['[', '{'].includes(doc[key][0])){
try{
doc[key] = JSON.parse(doc[key])
}catch(e){
message.warn(key +': json format error');
return false;
}
}
}
return doc;
}
export default {
namespace: "document",
state: {
index: '',
editingKey: '',
isLoading: false,
},
effects: {
*fetchDocList({payload}, {call, put}){
yield put({
type: 'saveData',
payload: {
isLoading: true,
}
});
let res = yield call(getDocList, payload);
if(res.errno != "0"){
message.warn("加载数据失败")
return
}
let indices = [];
if(res.payload.data && res.payload.data.length > 0){
for(let doc of res.payload.data){
if(!indices.includes(doc._index)){
indices.push(doc._index);
}
encodeObjectField(doc);
}
}
yield put({
type: 'saveData',
payload: {
pageIndex: payload.pageIndex,
pageSize: payload.pageSize,
isLoading: false,
index: payload.index,
indices,
cluster: payload.cluster || '',
filter: payload.filter || '',
...res.payload,
}
})
},
*saveDocItem({payload}, {call, put, select}){
yield put({
type: 'saveData',
payload: {
isLoading: true,
}
});
let doc = payload.data;
//let {rawData} = yield select(state => state.document);
if(decodeObjectField(doc) === false){
return;
}
let res = yield call(saveDoc, payload);
if(res.errno != "0"){
message.warn("保存数据失败")
return
}
encodeObjectField(doc);
yield put({
type: '_saveDocItem',
payload: {
docItem: payload.data,
extra: {
editingKey: '',
isLoading: false,
_index: ''
}
}
})
},
*deleteDocItem({payload}, {call, put}){
yield put({
type: 'saveData',
payload: {
isLoading: true,
}
});
let res = yield call(deleteDoc, payload);
if(res.errno != "0"){
message.warn("删除数据失败")
return
}
yield put({
type: '_deleteDocItem',
payload: {
docItem: payload.data,
extra: {
isLoading: false,
}
}
})
},
*addDocItem({payload},{call, put}){
yield put({
type: 'saveData',
payload: {
isLoading: true,
}
});
if(decodeObjectField(payload.data) === false){
return;
}
let res = yield call(addDoc, payload);
if(res.errno != "0"){
message.warn("添加文档失败")
return
}
encodeObjectField(res.payload);
yield put({
type: '_addNew',
payload: {
docItem: res.payload,
extra: {
isLoading: false,
}
}
})
}
},
reducers: {
saveData(state, {payload}){
return {
...state,
...payload,
}
},
_saveDocItem(state, {payload}){
let idx = state.data.findIndex((item) => {
return item.id == payload.docItem.id;
});
idx > -1 && (state.data[idx] = {
...state.data[idx],
...payload.docItem,
})
return {
...state,
...payload.extra,
};
},
_deleteDocItem(state, {payload}){
let idx = state.data.findIndex((item) => {
return item.id == payload.docItem.id;
});
state.data.splice(idx, 1);
return {
...state,
...payload.extra
};
},
_addNew(state, {payload}){
if(payload.extra && payload.extra.isAddNew){
state.data.unshift(payload.docItem)
return {
...state,
...payload.extra,
}
}else{
state.data[0] = payload.docItem;
return {
...state,
...payload.extra
}
}
},
cancelNew(state,{payload}){
state.data.shift()
return {
...state,
isAddNew: false,
}
}
}
}

View File

@ -15,7 +15,7 @@ const UpdateForm = Form.create()(props => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
let upVals = Object.assign(values, fieldsValue);
let upVals = Object.assign(_.cloneDeep(values), fieldsValue);
handleUpdate(upVals);
});
};
@ -69,33 +69,22 @@ class Pro extends React.Component {
super(props);
this.handleUpdate = this.handleUpdate.bind(this);
}
state = {
updateFormValues: null,
currentFormOp: null,
data: [],
search: {
size: 6,
name: "",
tags: "",
pageIndex: 1,
}
}
componentDidMount(){
this.fetchData();
const {size} = this.props.dict.search;
this.fetchData({
from: 0,
size: size,
});
}
handleReset = ()=>{
const {form} = this.props;
form.resetFields();
this.setState({
search: {
size: 6,
name: "",
tags: "",
pageIndex: 1,
}
},()=>{
this.fetchData();
this.fetchData({
from: 0,
size: this.props.dict.search.size,
name: '',
tags: ''
});
}
@ -104,13 +93,11 @@ class Pro extends React.Component {
let me = this;
form.validateFields((err, fieldsValue) => {
if (err) return;
me.setState({search:{
...me.state.search,
pageIndex: 1,
me.fetchData({
form: 0,
size: me.props.dict.search.size,
name: fieldsValue.name,
tags: fieldsValue.tags.join(',')
}},()=>{
me.fetchData();
})
})
}
@ -161,18 +148,24 @@ class Pro extends React.Component {
);
}
handleNewClick = () => {
this.setState({
const {dispatch} = this.props;
dispatch({
type: 'dict/saveData',
payload: {
currentFormOp: 'NEW',
updateFormValues: {},
formTitle: '添加词典'
}
})
}
handleDelete = (item) =>{
const {dispatch} = this.props;
const {dispatch, dict} = this.props;
const search = dict.search;
const me = this;
Modal.confirm({
title: '删除Pipeline',
content: '确定删除该Pipeline吗?',
title: '删除词典',
content: '确定删除该词典吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
@ -188,15 +181,19 @@ class Pro extends React.Component {
}
handleModifyClick = (item)=>{
this.setState({
const {dispatch} = this.props;
dispatch({
type: 'dict/saveData',
payload: {
updateFormValues: item,
formTitle: '修改词典',
currentFormOp: 'UPDATE',
})
}
});
}
handleUpdate(values){
let {currentFormOp, data} = this.state;
let {currentFormOp} = this.props.dict;
const {dispatch} = this.props;
let me = this;
switch(currentFormOp){
@ -204,69 +201,51 @@ class Pro extends React.Component {
dispatch({
type: 'dict/addDictItem',
payload: values,
callback: ()=>{
me.setState({
updateFormValues: null,
currentFormOp: null
});
}
});
break;
case "UPDATE":
dispatch({
type: 'dict/updateDictItem',
payload: values,
callback: ()=>{
me.setState({
updateFormValues: null,
currentFormOp: null
});
}
});
}
}
fetchData = ()=>{
fetchData = (params)=>{
const {dispatch} = this.props;
let {size, pageIndex} = this.state.search;
let me = this;
dispatch({
type: 'dict/fetchDictList',
payload: {
from: (pageIndex - 1) * size,
...this.state.search
},
callback: ()=>{
}
payload: params
});
}
handlePageChange = (p)=>{
this.setState({
search: {
...this.state.search,
let search = this.props.dict.search;
this.fetchData({
...search,
from: (p-1) * search.size,
pageIndex: p,
}
},()=>{
this.fetchData();
})
});
}
render(){
let data = this.props.dict ? this.props.dict.dictList : [];
let total = this.props.dict ? this.props.dict.total: 0;
let updateFormValues = this.state.updateFormValues;
let size = this.state.search.size;
//console.log('render');
let {dictList, search, updateFormValues, total, formTitle} = this.props.dict;
let {pageIndex, size} = search;
const {dispatch} = this.props;
const updateMethods = {
handleUpdate: this.handleUpdate,
handleUpdateModalVisible: ()=>{
this.setState({
dispatch({
type:'dict/saveData',
payload: {
updateFormValues: null,
currentFormOp: null,
})
}
});
}
};
console.log('render');
return (
<div>
<Card bordered={false} bodyStyle={{paddingBottom:0}} >
@ -278,11 +257,11 @@ class Pro extends React.Component {
bordered={false}>
<List
className="dic-list"
dataSource={data}
dataSource={dictList}
pagination={{
pageSize: size,
total: total,
current: this.state.search.pageIndex,
current: pageIndex,
onChange: this.handlePageChange
}}
grid={{
@ -320,7 +299,7 @@ class Pro extends React.Component {
</Card>
{updateFormValues ? (
<UpdateForm
title={this.state.formTitle}
title={formTitle}
{...updateMethods}
values={updateFormValues}
/>

View File

@ -50,9 +50,18 @@ export default {
namespace: 'dict',
state: {
updateFormValues: null,
currentFormOp: null,
formTitle: '',
search: {
size: 6,
name: "",
tags: "",
pageIndex: 1,
}
},
effects: {
*fetchDictList({payload, callback}, {call, put}){
*fetchDictList({payload}, {call, put}){
const resp = yield call(getDictList, payload);
if(resp.errno != "0" || !resp.data.Result){
return
@ -61,21 +70,26 @@ export default {
item.content = utf8.decode(atob(item.content))
return item;
})
let search = {name:'', tags: ''};
payload.pageIndex = payload.from / payload.size + 1;
search = Object.assign(search, payload);
yield put({
type: 'saveData',
payload: {
dictList: resp.data.Result,
total: resp.data.Total,
...payload,
search: search,
},
});
if(callback && typeof callback == 'function'){
callback(resp);
}
//message.loading('数据加载完成', 'initdata');
},
*addDictItem({payload, callback}, {call, put}){
const rel = yield call(addDict, payload);
*addDictItem({payload}, {call, put}){
let upVals = {
...payload,
}
upVals.content = btoa(utf8.encode(upVals.content));
const rel = yield call(addDict, upVals);
if(rel.errno != "0"){
message.warn('添加失败:'+ rel.errmsg)
return
@ -83,25 +97,34 @@ export default {
rel.payload.content = utf8.decode(atob(rel.payload.content));
yield put({
type: 'addDict',
payload: rel.payload,
})
if(callback && typeof callback == 'function'){
callback(rel);
payload: {
dictItem: rel.payload,
extra: {
updateFormValues: null,
currentFormOp: null
}
}
})
},
*updateDictItem({payload, callback}, {call, put}){
*updateDictItem({payload}, {call, put}){
let rawContent = payload.content;
payload.content = btoa(utf8.encode(payload.content));
const rel = yield call(updateDict, payload);
if(rel.errno != "0"){
message.warn('修改:'+ rel.errmsg)
return
}
payload.content = rawContent;
yield put({
type: 'updateDict',
payload: payload,
})
if(callback && typeof callback == 'function'){
callback(rel);
payload: {
dictItem: payload,
extra:{
updateFormValues: null,
currentFormOp: null
}
}
})
},
*deleteDictItem({payload}, {call, put, select}){
let rel = yield call(deleteDict, payload);
@ -112,14 +135,20 @@ export default {
message.warn('删除失败:'+ rel.errmsg)
return
}
const state = yield select(state => state.dict)
yield put({
type: 'fetchDictList',
type:'deleteDict',
payload: {
from: state.from,
size: state.size,
dictItem: payload,
}
})
});
//const search = yield select(state => state.dict.search)
// yield put({
// type: 'fetchDictList',
// payload: search,
// })
//yield take('fetchDictList/@@end')
}
},
reducers: {
@ -131,17 +160,35 @@ export default {
},
addDict(state, {payload}){
let dictList = state.dictList || [];
dictList.unshift(payload);
dictList.unshift(payload.dictItem);
state.dictList = dictList;
return state;
return {
...state,
...payload.extra,
total: state.total + 1,
};
},
updateDict(state, {payload}){
let cdata = state.dictList;
let idx = cdata.findIndex((item)=>{
return item.id == payload.id;
return item.id == payload.dictItem.id;
})
idx > -1 && (cdata[idx] = values);
return state;
idx > -1 && (cdata[idx] = payload.dictItem);
return {
...state,
...payload.extra,
}
},
deleteDict(state, {payload}){
let cdata = state.dictList;
let idx = cdata.findIndex((item)=>{
return item.id == payload.dictItem.id;
})
idx > -1 && (state.dictList.splice(idx, 1));
return {
...state,
total: state.total - 1,
}
}
},
};

44
web/src/services/doc.js Normal file
View File

@ -0,0 +1,44 @@
import request from '@/utils/request';
export async function getDocList(params) {
return request(`/api/doc/${params.index}`, {
method: 'POST',
body: {
action: 'SEARCH',
...params,
},
});
}
export async function saveDoc(params) {
return request(`/api/doc/${params.index}`, {
method: 'POST',
body: {
payload: params.data,
index: params.index,
action: 'SAVE',
},
});
}
export async function deleteDoc(params) {
return request(`/api/doc/${params.index}`, {
method: 'POST',
body: {
index: params.index,
action: 'DELETE',
payload: params.data,
},
});
}
export async function addDoc(params) {
return request(`/api/doc/${params.index}`, {
method: 'POST',
body: {
payload: params.data,
index: params.index,
action: 'ADD',
},
});
}