diff --git a/api/index_management/indices.go b/api/index_management/indices.go index 6cf31bc2..73e9d604 100644 --- a/api/index_management/indices.go +++ b/api/index_management/indices.go @@ -92,4 +92,19 @@ func (handler APIHandler) HandleUpdateSettingsAction(w http.ResponseWriter, req } resBody["payload"] = true handler.WriteJSON(w, resBody, http.StatusOK) +} + +func (handler APIHandler) HandleDeleteIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { + client := elastic.GetClient(handler.Config.Elasticsearch) + indexName := ps.ByName("index") + resBody := newResponseBody() + err := client.DeleteIndex(indexName) + if err != nil { + resBody["status"] = false + resBody["error"] = err + handler.WriteJSON(w, resBody, http.StatusOK) + return + } + resBody["payload"] = true + handler.WriteJSON(w, resBody, http.StatusOK) } \ No newline at end of file diff --git a/api/init.go b/api/init.go index e92756cf..9a15b7f0 100644 --- a/api/init.go +++ b/api/init.go @@ -33,6 +33,7 @@ func Init(cfg *config.AppConfig) { ui.HandleUIMethod(api.GET, pathPrefix+"index/:index/_mappings", handler.HandleGetMappingsAction) ui.HandleUIMethod(api.GET, pathPrefix+"index/:index/_settings", handler.HandleGetSettingsAction) ui.HandleUIMethod(api.PUT, pathPrefix+"index/:index/_settings", handler.HandleUpdateSettingsAction) + ui.HandleUIMethod(api.DELETE, pathPrefix+"index/:index", handler.HandleDeleteIndexAction) task.RegisterScheduleTask(task.ScheduleTask{ Description: "sync reindex task result to index infinireindex", diff --git a/web/config/router.config.js b/web/config/router.config.js index f6c8c062..125e6775 100644 --- a/web/config/router.config.js +++ b/web/config/router.config.js @@ -18,7 +18,7 @@ export default [ authority: ['admin', 'user'], routes: [ // dashboard - { path: '/', redirect: '/platform/cluster' }, + { path: '/', redirect: '/platform/clusterlist' }, { path: '/platform', name: 'platform', diff --git a/web/mock/datamanagement/indices.js b/web/mock/datamanagement/indices.js index 2626019f..ffd5549f 100644 --- a/web/mock/datamanagement/indices.js +++ b/web/mock/datamanagement/indices.js @@ -648,10 +648,10 @@ const mappings = { }; export default { - 'get /_search-center/_cat/indices': function(req, res){ - res.send(data) - }, - 'get /_search-center/index/:index/_mappings': function(req, res){ - res.send(mappings) - } + // 'get /_search-center/_cat/indices': function(req, res){ + // res.send(data) + // }, + // 'get /_search-center/index/:index/_mappings': function(req, res){ + // res.send(mappings) + // } } \ No newline at end of file diff --git a/web/src/lib/elasticsearch/edit_settings.js b/web/src/lib/elasticsearch/edit_settings.js new file mode 100644 index 00000000..4213f2be --- /dev/null +++ b/web/src/lib/elasticsearch/edit_settings.js @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import {flattenObject} from '@/lib/flatten_object'; +import _ from 'lodash'; + +export const readOnlySettings = [ + 'index.creation_date', + 'index.number_of_shards', + 'index.provided_name', + 'index.uuid', + 'index.version.created', + 'index.compound_format', + 'index.data_path', + 'index.format', + 'index.number_of_routing_shards', + 'index.sort.field', + 'index.sort.missing', + 'index.sort.mode', + 'index.sort.order', + 'index.routing_partition_size', + 'index.store.type', +]; +export const settingsToDisplay = [ + 'index.number_of_replicas', + 'index.blocks.read_only_allow_delete', + 'index.codec', + 'index.priority', + 'index.query.default_field', + 'index.refresh_interval', + 'index.write.wait_for_active_shards', +]; + +//API expects settings in flattened dotted form, +//whereas they come back as nested objects from ES +export function transformSettingsForApi(data, isOpen) { + const { defaults, settings } = data; + //settings user has actually set + const flattenedSettings = flattenObject(settings); + //settings with their defaults + const flattenedDefaults = flattenObject(defaults); + const filteredDefaults = _.pick(flattenedDefaults, settingsToDisplay); + const newSettings = { ...filteredDefaults, ...flattenedSettings }; + readOnlySettings.forEach((e) => delete newSettings[e]); + //can't change codec on open index + if (isOpen) { + delete newSettings['index.codec']; + } + return newSettings; +} \ No newline at end of file diff --git a/web/src/lib/flatten_object.js b/web/src/lib/flatten_object.js new file mode 100644 index 00000000..fa6524f4 --- /dev/null +++ b/web/src/lib/flatten_object.js @@ -0,0 +1,17 @@ +import _ from 'lodash'; + +export const flattenObject = (nestedObj, flattenArrays) => { + const stack = []; // track key stack + const flatObj = {}; + const dot = '.'; + (function flattenObj(obj) { + _.keys(obj).forEach(function (key) { + stack.push(key); + if (!flattenArrays && Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; + else if (_.isObject(obj[key])) flattenObj(obj[key]); + else flatObj[stack.join(dot)] = obj[key]; + stack.pop(); + }); + })(nestedObj); + return flatObj; +}; \ No newline at end of file diff --git a/web/src/pages/DataManagement/Index.js b/web/src/pages/DataManagement/Index.js index 1e17dbbc..761b674c 100644 --- a/web/src/pages/DataManagement/Index.js +++ b/web/src/pages/DataManagement/Index.js @@ -1,5 +1,6 @@ import React, { PureComponent, Fragment } from 'react'; import { connect } from 'dva'; +import {Link} from 'umi'; import { Row, Col, @@ -14,14 +15,14 @@ import { Tabs, Descriptions, Menu, + Table, Dropdown, - Icon + Icon, Popconfirm } from 'antd'; -import StandardTable from '@/components/StandardTable'; -import PageHeaderWrapper from '@/components/PageHeaderWrapper'; +import Editor from '@monaco-editor/react'; import styles from '../List/TableList.less'; -import JSONPretty from 'react-json-prettify'; +import {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings'; const FormItem = Form.Item; const { TextArea } = Input; @@ -132,9 +133,8 @@ const UpdateForm = Form.create()(props => { }); /* eslint react/no-multi-comp:0 */ -@connect(({ pipeline, loading }) => ({ - pipeline, - loading: loading.models.pipeline, +@connect(({ index }) => ({ + index })) @Form.create() class Index extends PureComponent { @@ -142,14 +142,12 @@ class Index extends PureComponent { modalVisible: false, updateModalVisible: false, expandForm: false, - selectedRows: [], formValues: {}, updateFormValues: {}, drawerVisible: false, editingIndex:{}, + indexActiveKey: '1', }; - 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: '索引名称', @@ -165,19 +163,15 @@ class Index extends PureComponent { }, { title: '文档数', - dataIndex: 'docs.count', + dataIndex: 'docs_count', }, { title: '主分片数', - dataIndex: 'pri' + dataIndex: 'shards' }, { title: '从分片数', - dataIndex: 'rep' - }, - { - title: '存储大小', - dataIndex: 'store.size' + dataIndex: 'replicas' }, { title: '操作', @@ -185,15 +179,11 @@ class Index extends PureComponent { {/* this.handleUpdateModalVisible(true, record)}>设置 */} - { - this.state.selectedRows.push(record); - this.handleDeleteClick(); - }}>删除 + this.handleDeleteClick(record.index)}> + 删除 + - { - this.state.selectedRows.push(record); - this.handleDeleteClick(); - }}>文档管理 + 文档管理 ), }, @@ -201,36 +191,13 @@ class Index extends PureComponent { 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, + type: 'index/fetchIndices', + payload: { + cluster: 'single-es' + } }); - }; + } handleFormReset = () => { const { form, dispatch } = this.props; @@ -244,50 +211,25 @@ class Index extends PureComponent { }); }; - handleDeleteClick = e => { + handleDeleteClick = (indexName) => { 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, + dispatch({ + type: 'index/removeIndex', + payload: { + index: indexName + } }); }; 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, + searchKey: fieldsValue.name, }); }); }; @@ -366,14 +308,63 @@ class Index extends PureComponent { return this.renderSimpleForm(); } + handleIndexTabChanged = (activeKey, indexName) => { + this.setState({ + indexActiveKey: activeKey, + }) + const {dispatch} = this.props; + if(activeKey == '2'){ + if(this.props.index.mappings[indexName]){ + return + } + dispatch({ + type: 'index/fetchMappings', + payload: { + index: indexName, + } + }) + }else if(activeKey == '4'){ + if(this.props.index.settings[indexName]){ + return + } + dispatch({ + type: 'index/fetchSettings', + payload: { + index: indexName, + } + }) + } + } + handleEditorDidMount = (_valueGetter)=>{ + this.indexSettingsGetter = _valueGetter; + } + + handleIndexSettingsSaveClick = (indexName)=>{ + let settings = this.indexSettingsGetter(); + settings = JSON.parse(settings); + const {dispatch} = this.props; + dispatch({ + type: 'index/saveSettings', + payload: { + index: indexName, + settings: settings.settings, + } + }) + } + render() { - const data = { - list: JSON.parse(this.datasource), - pagination: { - pageSize: 5, - }, - }; - const { selectedRows, modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state; + const {clusterIndices, settings} = this.props.index; + let indices = []; + for(let key in clusterIndices) { + if(this.state.searchKey){ + if(key.indexOf(this.state.searchKey) > -1){ + indices.push(clusterIndices[key]); + } + continue + } + indices.push(clusterIndices[key]); + } + const { modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state; const parentMethods = { handleAdd: this.handleAdd, handleModalVisible: this.handleModalVisible, @@ -382,6 +373,10 @@ class Index extends PureComponent { handleUpdateModalVisible: this.handleUpdateModalVisible, handleUpdate: this.handleUpdate, }; + let newSettings = {}; + if(settings && settings[editingIndex.index]){ + newSettings = transformSettingsForApi(settings[editingIndex.index], editingIndex.status === 'open') + } return ( @@ -392,18 +387,14 @@ class Index extends PureComponent { - {selectedRows.length > 0 && ( - - - - )} - @@ -420,20 +411,21 @@ class Index extends PureComponent { onClose={()=>{ this.setState({ drawerVisible: false, + indexActiveKey: '1', }); }} - width={640} + width={720} > - {}}> + {this.handleIndexTabChanged(activeKey, editingIndex.index)}}> - green - open - 1 - 0 - 5 - 0 - 115.3kb + {editingIndex.health} + {editingIndex.status} + {editingIndex.shards} + {editingIndex.replicas} + {editingIndex.docs_count} + {editingIndex.docs_deleted} + @@ -441,140 +433,38 @@ class Index extends PureComponent { - - - - Content of Tab Pane 3 +
+
{JSON.stringify(this.props.index.mappings[editingIndex.index], null, 2)}
+
+
+ {/**/} + {/* Content of Tab Pane 3*/} + {/**/} - Content of Tab Pane 3 +
+ Edit, then save your JSON +
+
+ +
@@ -586,10 +476,10 @@ class Index extends PureComponent { Delete - - - Edit - + {/**/} + {/* */} + {/* Edit*/} + {/**/} Close diff --git a/web/src/pages/DataManagement/models/index.js b/web/src/pages/DataManagement/models/index.js new file mode 100644 index 00000000..7ef329bf --- /dev/null +++ b/web/src/pages/DataManagement/models/index.js @@ -0,0 +1,92 @@ +import { getIndices,getMappings, getSettings, deleteIndex, +updateSettings} from '@/services/indices'; +import { message } from 'antd'; + +export default { + namespace: 'index', + state: { + clusterIndices: [], + mappings: {}, + settings: {} + }, + effects:{ + *fetchIndices({payload}, {call, put}){ + let resp = yield call(getIndices) + if(resp.status === false){ + message.warn("获取数据失败") + return + } + yield put({ + type: 'saveData', + payload: { + clusterIndices: resp.payload, + cluster: payload.cluster, + } + }) + }, + *fetchMappings({payload}, {call, put}){ + let resp = yield call(getMappings, payload); + if(resp.status === false){ + message.warn("get mappings failed") + return + } + yield put({ + type: 'saveData', + payload: { + mappings: resp.payload, + } + }) + }, + *fetchSettings({payload}, {call, put}){ + let resp = yield call(getSettings, payload); + if(resp.status === false){ + message.warn("get settings failed") + return + } + yield put({ + type: 'saveData', + payload: { + settings: resp.payload, + } + }) + }, + *saveSettings({payload}, {call, put, select}){ + let resp = yield call(updateSettings, payload); + if(resp.status === false){ + message.warn("save settings failed") + return + } + let {settings} = yield select(state=>state.index); + settings[payload.index] = payload.settings; + yield put({ + type: 'saveData', + payload: { + settings + } + }) + }, + *removeIndex({payload}, {call, put, select}){ + let resp = yield call(deleteIndex, payload); + if(resp.status === false){ + message.warn("get mappings failed") + return + } + let {clusterIndices} = yield select(state=>state.index); + delete clusterIndices[payload.index]; + yield put({ + type: 'saveData', + payload: { + clusterIndices: clusterIndices, + } + }) + } + }, + reducers:{ + saveData(state, {payload}){ + return { + ...state, + ...payload, + } + } + } +} \ No newline at end of file diff --git a/web/src/services/indices.js b/web/src/services/indices.js index 709da9a2..25b0457a 100644 --- a/web/src/services/indices.js +++ b/web/src/services/indices.js @@ -10,9 +10,34 @@ export async function getMappings(payload){ }); } +export async function getSettings(payload){ + let index = payload.index || '*' + let url = `${pathPrefix}/index/${index}/_settings`; + return request(url,{ + method: 'GET', + expirys: 0, + }); +} + +export async function updateSettings(payload){ + let index = payload.index + let url = `${pathPrefix}/index/${index}/_settings`; + return request(url,{ + method: 'PUT', + body: payload.settings, + expirys: 0, + }); +} export async function getIndices(params) { return request(`${pathPrefix}/_cat/indices`, { method: 'GET' }); +} + +export async function deleteIndex(params) { + let index = params.index; + return request(`${pathPrefix}/index/${index}`, { + method: 'DELETE' + }); } \ No newline at end of file