diff --git a/api/index_management/document.go b/api/index_management/document.go index 7d4f2c97..991cc30e 100644 --- a/api/index_management/document.go +++ b/api/index_management/document.go @@ -1,8 +1,10 @@ package index_management import ( + "encoding/json" "fmt" "net/http" + "strings" httprouter "infini.sh/framework/core/api/router" "infini.sh/framework/core/elastic" @@ -30,7 +32,10 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. } err := handler.DecodeJSON(req, &reqBody) if err != nil { - panic(err) + resResult["errno"] = "E10001" + resResult["errmsg"] = err.Error() + handler.WriteJSON(w, resResult, http.StatusOK) + return } indexName := ps.ByName("index") var id string @@ -46,18 +51,23 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. //security problem _, err := client.Index(indexName, id, reqBody.Payload) if err != nil { - panic(err) + resResult["errno"] = "E10002" + resResult["errmsg"] = err.Error() + break } reqBody.Payload["id"] = id resResult["payload"] = reqBody.Payload - handler.WriteJSON(w, resResult, http.StatusOK) case "SAVE": if id == "" { - panic("empty id") + resResult["errno"] = "E10003" + resResult["errmsg"] = "empty id" + break } resp, err := client.Get(indexName, id) if err != nil { - panic(err) + resResult["errno"] = "E10004" + resResult["errmsg"] = err.Error() + break } source := resp.Source for k, v := range reqBody.Payload { @@ -65,9 +75,10 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. } _, err = client.Index(indexName, id, source) if err != nil { - panic(err) + resResult["errno"] = "E10005" + resResult["errmsg"] = err.Error() + break } - handler.WriteJSON(w, resResult, http.StatusOK) case "DELETE": if id == "" { @@ -76,11 +87,9 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. _, err = client.Delete(indexName, id) if err != nil { resResult["errmsg"] = err.Error() - resResult["errno"] = "E100003" - handler.WriteJSON(w, resResult, http.StatusOK) - return + resResult["errno"] = "E10006" + break } - handler.WriteJSON(w, resResult, http.StatusOK) default: var ( pageSize = 10 @@ -101,21 +110,66 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. filter = reqBody.Filter } query := fmt.Sprintf(`{"from":%d, "size": %d, "query": %s}`, from, pageSize, filter) - fmt.Println(query) + fmt.Println(indexName, query) var reqBytes = []byte(query) resp, err := client.SearchWithRawQueryDSL(indexName, reqBytes) if err != nil { - panic(err) + resResult["errno"] = "E10007" + resResult["errmsg"] = err.Error() + break } - result := formatESSearchResult(resp) - handler.WriteJSON(w, map[string]interface{}{ - "errno": "0", - "errmsg": "", - "payload": result, - }, http.StatusOK) + _, _, idxs, err := client.GetMapping(false, indexName) + if err != nil { + resResult["errno"] = "E10008" + resResult["errmsg"] = err.Error() + break + } + result["mappings"] = idxs + resResult["payload"] = result + } + handler.WriteJSON(w, resResult, http.StatusOK) +} + +func (handler APIHandler) HandleGetIndicesAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { + indices, err := getESIndices(handler.Config.Elasticsearch) + if err != nil { + panic(err) + } + + handler.WriteJSON(w, map[string]interface{}{ + "errno": "0", + "errmsg": "", + "payload": indices, + }, http.StatusOK) +} + +func getESIndices(esName string) ([]string, error) { + client := elastic.GetClient(esName) + esConfig := elastic.GetConfig(esName) + url := fmt.Sprintf("%s/_cat/indices?format=json", esConfig.Endpoint) + result, err := client.Request("GET", url, nil) + if err != nil { + return nil, err + } + var catIndices = []struct { + Index string `json:"index"` + }{} + err = json.Unmarshal(result.Body, &catIndices) + if err != nil { + return nil, err + } + var indices = []string{} + for _, index := range catIndices { + if strings.HasPrefix(index.Index, ".") { + continue + } + indices = append(indices, index.Index) + } + + return indices, nil } func formatESSearchResult(esResp *elastic.SearchResponse) map[string]interface{} { diff --git a/api/init.go b/api/init.go index 9a170c3a..7709bcb8 100644 --- a/api/init.go +++ b/api/init.go @@ -19,4 +19,5 @@ func Init(cfg *config.AppConfig) { //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) + ui.HandleUIMethod(api.GET, "/api/indices/_cat", handler.HandleGetIndicesAction) } diff --git a/web/src/pages/DataManagement/Document.js b/web/src/pages/DataManagement/Document.js index 4a1b2828..daaecc2a 100644 --- a/web/src/pages/DataManagement/Document.js +++ b/web/src/pages/DataManagement/Document.js @@ -2,9 +2,9 @@ import React, { Component, createRef } from 'react'; import router from 'umi/router'; import { connect } from 'dva'; import { Col, Form, Row,Select, Input, Card,Icon, Table, InputNumber, Popconfirm, - Divider,Button,Tooltip, Cascader, Modal } from 'antd'; + Divider,Button,Tooltip, Cascader, Modal, DatePicker, Dropdown,Menu, message } from 'antd'; import Editor, {monaco} from '@monaco-editor/react'; - +import moment from 'moment'; import {createDependencyProposals} from './autocomplete'; function findParentIdentifier(textUntilPosition){ @@ -47,35 +47,44 @@ function findParentIdentifier(textUntilPosition){ return identifer.reverse().join(''); } +// monaco.config({ +// paths: { +// vs: '...', +// }, +// 'vs/nls' : { +// availableLanguages: { +// '*': 'de', +// }, +// }, +// }); monaco.init().then((mi)=>{ - mi.languages.registerCompletionItemProvider('json', { - provideCompletionItems: function(model, position) { - // find out if we are completing a property in the 'dependencies' object. - var textUntilPosition = model.getValueInRange({startLineNumber: 1, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column}); - - if(textUntilPosition.indexOf('{') < 0){ - return { suggestions: [] }; - } - - let key = findParentIdentifier(textUntilPosition); - //console.log(key); - // var match = textUntilPosition.match(/"match"\s*:\s*\{\s*("[^"]*"\s*:\s*"[^"]*"\s*,\s*)*([^"]*)?$/); - // if (!match) { - // return { suggestions: [] }; - // } - var word = model.getWordUntilPosition(position); - - var range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn - }; - return { - suggestions: createDependencyProposals(key, range, mi) - }; - } - }); + mi.languages.onLanguage("json", ()=>{ + mi.languages.registerCompletionItemProvider('json', { + triggerCharacters: ['"'], + provideCompletionItems: function(model, position, ctx) { + var textUntilPosition = model.getValueInRange({startLineNumber: 1, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column}); + + if(textUntilPosition.indexOf('{') < 0){ + return { suggestions: [] }; + } + + let key = findParentIdentifier(textUntilPosition); + var word = model.getWordUntilPosition(position); + + var range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + + //console.log(ctx, range,textUntilPosition) + return { + suggestions: createDependencyProposals(key, range, mi, ctx.triggerCharacter) + }; + } + }); + }) }) @@ -85,11 +94,16 @@ const EditableContext = React.createContext(); class EditableCell extends React.Component { getInput = () => { - let {record, dataIndex} = this.props; - if (typeof record[dataIndex] === 'number') { - return ; + let {record, dataIndex, type} = this.props; + if(['byte','short', 'integer', 'long'].includes(type)){ + return ; + }else if(type == 'date'){ + return } - return ; + // if (typeof record[dataIndex] === 'number') { + // return ; + // } + return ; }; renderCell = ({ getFieldDecorator }) => { @@ -100,8 +114,17 @@ class EditableCell extends React.Component { record, index, children, + type, ...restProps } = this.props; + let initialValue = ''; + if(editing){ + if(type=='date'){ + initialValue = moment(record[dataIndex]); + }else{ + initialValue = record[dataIndex]; + } + } return ( {editing ? ( @@ -113,7 +136,7 @@ class EditableCell extends React.Component { message: `Please Input ${title}!`, }, ], - initialValue: record[dataIndex], + initialValue: initialValue, })(this.getInput())} ) : ( @@ -169,6 +192,16 @@ class EditableCell extends React.Component { } isEditing = record => record.id === this.props.doclist.editingKey; + + getFieldType = (record, key)=>{ + const {doclist} = this.props; + // if(!doclist.mappings[record._index]){ + // console.log(record, doclist.mappings) + // return + // } + const {properties} = doclist.mappings[record._index].mappings; + return properties[key].type; + } cancel = () => { const {dispatch, doclist} = this.props; @@ -188,7 +221,7 @@ class EditableCell extends React.Component { save(form, key) { const {dispatch,doclist} = this.props; form.validateFields((error, row) => { - if (error) { + if (error) { return; } //console.log(row, key, doclist._index); @@ -246,8 +279,16 @@ class EditableCell extends React.Component { }) } - onShowSizeChange(current, pageSize) { - console.log(current, pageSize); + onShowSizeChange = (current, pageSize)=> { + const {fetchData, doclist} = this.props; + fetchData({ + pageIndex: current, + pageSize: pageSize, + index: doclist.index, + cluster: doclist.cluster, + filter: doclist.filter, + }) + //console.log(current, pageSize); } render() { @@ -268,6 +309,7 @@ class EditableCell extends React.Component { dataIndex: key, title: key, editing: this.isEditing(record), + type: this.getFieldType(record, key), // onMouseEnter: event => {console.log(event)}, // onMouseLeave: event => {}, }), @@ -299,8 +341,8 @@ class EditableCell extends React.Component { rowClassName="editable-row" pagination={{ onChange: this.cancel, - //showSizeChanger: true, - //onShowSizeChange: this.onShowSizeChange, + showSizeChanger: true, + onShowSizeChange: this.onShowSizeChange, total: doclist.total? doclist.total.value: 0, pageSize: doclist.pageSize, current: doclist.pageIndex, @@ -314,6 +356,53 @@ class EditableCell extends React.Component { } } +class InputSelect extends React.Component{ + state = { + value: this.props.defaultValue + } + onClick = ({ key }) => { + this.inputEl.setState({ + value: key + }) + this.setState({ + value: key, + }) + this.triggerChange(key) + } + triggerChange = (val)=>{ + let {onChange} = this.props; + if(onChange && typeof onChange == 'function'){ + onChange(val) + } + } + handleChange = (ev) => { + let val = ev.target.value; + let filterData = this.props.data.slice(); + if(val != ""){ + filterData = filterData.filter(v=>v.value.includes(val)) + } + this.setState({ + value: val, + data: filterData + }) + this.triggerChange(val); + } + render(){ + let {data, onChange, ...restProps} = this.props; + let filterData = this.state.data || data; + return ( + + {filterData.map(op =>( + {op.label} + ))} + + } trigger={['focus']}> + {this.inputEl=el}} {...restProps} onChange={this.handleChange}/> + + ) + } +} @connect(({document})=>({ document @@ -341,29 +430,35 @@ class Doucment extends React.Component { } componentDidMount(){ - const {location } = this.props; + const {location, dispatch } = this.props; //console.log(match, location); let index = location.query.index || 'infini-test'; let cluster = location.query.cluster || 'single-es'; - this.indexEl.setState({ - value: [cluster, index] - }) - this.fetchData({ + dispatch({ + type: 'document/fetchIndices', + payload: { + cluster, + } + }).then(()=>{ + this.fetchData({ pageSize: 10, pageIndex: 1, + cluster, index, + }) }) + } handleNewClick = ()=>{ const {dispatch, document} = this.props; - if(!document.data || document.data.length == 0 || document.isAddNew){ + if(document.isAddNew){ //!document.data || document.data.length == 0 return; } - let {indices} = document; + let {indices, mappings} = document; let _index = indices[0]; if(indices && indices.length > 1){ - console.log(this.indexSelEl); + //console.log(this.indexSelEl); let vals = this.indexSelEl.rcSelect.state.value; if(vals.length == 0){ Modal.error({ @@ -375,8 +470,8 @@ class Doucment extends React.Component { _index = vals[0]; } } - let keys = Object.keys(document.data[0]) - let newDoc = {}; + let keys = Object.keys(mappings[_index].mappings.properties) + let newDoc = {id:"", _index}; for(let key of keys){ newDoc[key] = "" } @@ -392,24 +487,29 @@ class Doucment extends React.Component { }) } - handleSearchClick = (value)=>{ - let [cluster, index] = this.indexEl.state.value; - let rewriteIndex = this.reindexEl.state.value; - if(typeof rewriteIndex !== 'undefined' && rewriteIndex !=""){ - index = rewriteIndex; + handleSearchClick = (e)=>{ + let value = this.keywordEl.state.value; + let index = this.indexEl.state.value; + let cluster = this.clusterEl.rcSelect.state.value[0]; + let filter = ''; + if(!cluster || !index){ + message.error('please select cluster and index'); + return; + } + if(this.state.bodyDisplay != 'none'){ + filter = this.filterGetter(); } - this.fetchData({ cluster, - index: index, - pageSize: 10, + index, + pageSize: this.props.document.pageSize, pageIndex: 1, //filter: this.filterEl.state.value, - filter: this.filterGetter(), + filter, keyword: value, }).then(()=>{ if(this.hashChanged){ - router.push(`/data/doc?index=${index}`); + router.push(`/data/doc?cluster=${cluster}&index=${index}`); this.hashChanged = !this.hashChanged; } }) @@ -436,41 +536,37 @@ class Doucment extends React.Component { 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', - }, - { - value: 'infini-test1', - label: 'infini-test1', - } - ]}]; + let clusterIndices = this.props.document.clusterIndices || []; + + clusterIndices = clusterIndices.map((index) =>{ + return { + label: index, + value: index, + }; + }) + const clusters = ["single-es"]; + let {cluster, index}= this.props.document; + cluster = cluster || this.props.location.query.cluster; + index = index || this.props.location.query.index; return (
- {this.indexEl=el}} - style={{width: '20%'}} - onChange={(values, selectedOptions)=>{this.reindexEl.setState({value: values[1]}); this.hashChanged = true;}} - placeholder="Please select index" - showSearch={{filter: (inputValue, path)=>path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1) }} - /> - {this.reindexEl=el}} placeholder="rewrite index or index pattern" style={{width: '20%'}}/> - this.clusterEl=el} defaultValue={cluster} style={{width: '20%'}}> + { + clusters.map(op=>({op})) + } + + {this.hashChanged=true;}} defaultValue={index} ref={el=>{this.indexEl=el}} placeholder="input index or index pattern" style={{width: '25%'}}/> + this.keywordEl=el} + placeholder="input search keyword" + disabled = {this.state.bodyDisplay != 'none'} /> + @@ -490,7 +586,7 @@ class Doucment extends React.Component { - + {/* {this.filterEl=el}} placeholder="input query filter (elasticsearch query DSL)" rows={8}/> */} + +
+
query example:
+   "bool":{
+     "must": {
+       "match":{
+         "FIELD": "VALUE"
+       }
+     }
+   }
}` }}> + +
+
+
diff --git a/web/src/pages/DataManagement/autocomplete.js b/web/src/pages/DataManagement/autocomplete.js index cdd48e84..7fe77cce 100644 --- a/web/src/pages/DataManagement/autocomplete.js +++ b/web/src/pages/DataManagement/autocomplete.js @@ -4,19 +4,19 @@ function registerKeyProposals(key){ 'bool': [{ label: 'filter', documentation: "filter", - insertText: '"filter": [\n\t{}\n]', + insertText: '"filter": [\n\t{$0}\n]', },{ label: 'minimum_should_match', insertText: '"minimum_should_match": 1', },{ label: 'must', - insertText: '"must": [\n\t{}\n]', + insertText: '"must": [\n\t{$0}\n]', },{ label: 'must_not', - insertText: '"must_not": [\n\t{}\n]', + insertText: '"must_not": [\n\t{$0}\n]', },{ label: 'should', - insertText: '"should": [\n\t{}\n]', + insertText: '"should": [\n\t{$0}\n]', }], 'multi_match':[{ label: 'analyzer', @@ -37,7 +37,7 @@ function registerKeyProposals(key){ label: 'type', insertText: '"type": "best_fields"', }] - }; + }; //${1|one,two,three|} if(proposals[key]){ return proposals[key]; }else{ @@ -45,21 +45,37 @@ function registerKeyProposals(key){ } } -export function createDependencyProposals(key, range, mi) { +export function createDependencyProposals(key, range, mi, triggerChar) { if(['', 'must', 'must_not', 'should'].includes(key)){ return queryProposals.map(p=>{ + let copyItem = { + ...p + }; + let insertText = copyItem.insertText; + triggerChar == '"' && (copyItem.insertText=insertText.slice(1)) && (range = { + ...range, + endColumn: range.endColumn +1 + }) return { kind: mi.languages.CompletionItemKind.Property, - ...p, + ...copyItem, insertTextRules: mi.languages.CompletionItemInsertTextRule.InsertAsSnippet, range: range } }) } return registerKeyProposals(key).map(p=>{ + let copyItem = { + ...p + }; + let insertText = copyItem.insertText; + triggerChar == '"' && (copyItem.insertText=insertText.slice(1)) && (range = { + ...range, + endColumn: range.endColumn +1 + }) return { kind: mi.languages.CompletionItemKind.Property, - ...p, + ...copyItem, insertTextRules: mi.languages.CompletionItemInsertTextRule.InsertAsSnippet, range: range } diff --git a/web/src/pages/DataManagement/models/document.js b/web/src/pages/DataManagement/models/document.js index f71ce904..b57c60a1 100644 --- a/web/src/pages/DataManagement/models/document.js +++ b/web/src/pages/DataManagement/models/document.js @@ -1,4 +1,4 @@ -import {getDocList, saveDoc, deleteDoc, addDoc} from '@/services/doc'; +import {getDocList, saveDoc, deleteDoc, addDoc, getIndices} from '@/services/doc'; import { message } from 'antd'; function encodeObjectField(doc){ @@ -48,7 +48,7 @@ export default { message.warn("加载数据失败") return } - let indices = []; + let indices = Object.keys(res.payload.mappings); //indices state can remove if(res.payload.data && res.payload.data.length > 0){ for(let doc of res.payload.data){ if(!indices.includes(doc._index)){ @@ -139,6 +139,7 @@ export default { return } encodeObjectField(res.payload); + res.payload['_index'] = payload.index; yield put({ type: '_addNew', payload: { @@ -148,8 +149,21 @@ export default { } } }) + }, + *fetchIndices({payload}, {call, put}){ + let resp = yield call(getIndices) + if(resp.errno != "0"){ + message.warn("获取数据失败") + return + } + yield put({ + type: 'saveData', + payload: { + clusterIndices: resp.payload, + cluster: payload.cluster, + } + }) } - }, reducers: { saveData(state, {payload}){ @@ -179,11 +193,18 @@ export default { state.data.splice(idx, 1); return { ...state, - ...payload.extra + ...payload.extra, + total: { + ...state.total, + value: state.total.value-1 + } }; }, _addNew(state, {payload}){ if(payload.extra && payload.extra.isAddNew){ + if(!state.data){ + state.data = []; + } state.data.unshift(payload.docItem) return { ...state, @@ -193,7 +214,11 @@ export default { state.data[0] = payload.docItem; return { ...state, - ...payload.extra + ...payload.extra, + total: { + ...state.total, + value: state.total.value + 1 + } } } }, diff --git a/web/src/services/doc.js b/web/src/services/doc.js index fcf80f37..0f1571da 100644 --- a/web/src/services/doc.js +++ b/web/src/services/doc.js @@ -41,4 +41,10 @@ export async function addDoc(params) { action: 'ADD', }, }); +} + +export async function getIndices(params) { + return request(`/api/indices/_cat`, { + method: 'GET' + }); } \ No newline at end of file