add new cluster overview and command manage

This commit is contained in:
liugq 2021-10-27 11:42:11 +08:00
parent 4b90b75a39
commit b8690455c4
19 changed files with 964 additions and 13 deletions

View File

@ -7,6 +7,8 @@ import (
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"net/http"
"strconv"
"strings"
"time"
)
@ -52,20 +54,55 @@ func (h *APIHandler) HandleQueryCommonCommandAction(w http.ResponseWriter, req *
resBody := map[string]interface{}{
}
//title := h.GetParameterOrDefault(req, "title", "")
//tag := h.GetParameterOrDefault(req, "search", "")
var (
title = h.GetParameterOrDefault(req, "title", "")
queryDSL = `{"query":{"bool":{"filter":[%s]}}, "size": %d, "from": %d}`
strSize = h.GetParameterOrDefault(req, "size", "20")
strFrom = h.GetParameterOrDefault(req, "from", "0")
filterBuilder = &strings.Builder{}
)
if title != ""{
filterBuilder.WriteString(fmt.Sprintf(`{"prefix":{"title": "%s"}}`, title))
}
size, _ := strconv.Atoi(strSize)
if size <= 0 {
size = 20
}
from, _ := strconv.Atoi(strFrom)
if from < 0 {
from = 0
}
queryDSL = fmt.Sprintf(queryDSL, filterBuilder.String(), size, from)
esClient := elastic.GetClient(h.Config.Elasticsearch)
//queryDSL :=[]byte(fmt.Sprintf(`{"query":{"bool":{"must":{"match":{"title":"%s"}}}}}`, title))
searchRes, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(elastic.CommonCommand{}),nil)
searchRes, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(elastic.CommonCommand{}), []byte(queryDSL))
if err != nil {
resBody["error"] = err
h.WriteJSON(w, resBody, http.StatusInternalServerError)
return
}
h.WriteJSON(w, searchRes,http.StatusOK)
}
func (h *APIHandler) HandleDeleteCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
resBody := map[string]interface{}{}
id := ps.ByName("cid")
esClient := elastic.GetClient(h.Config.Elasticsearch)
delRes, err := esClient.Delete(orm.GetIndexName(elastic.CommonCommand{}), "", id, "wait_for")
if err != nil {
resBody["error"] = err.Error()
if delRes!=nil{
h.WriteJSON(w, resBody, delRes.StatusCode)
}else{
h.WriteJSON(w, resBody, http.StatusInternalServerError)
}
return
}
elastic.RemoveInstance(id)
resBody["_id"] = id
resBody["result"] = delRes.Result
h.WriteJSON(w, resBody, delRes.StatusCode)
}

View File

@ -44,6 +44,7 @@ func Init(cfg *config.AppConfig) {
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleSaveCommonCommandAction)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleDeleteCommonCommandAction)
//new api
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)

View File

@ -1,8 +1,8 @@
package config
const LastCommitLog = "b8fb6a3, Fri Oct 15 11:41:38 2021 +0800, liugq, console tab v0.1 "
const BuildDate = "2021-10-21 13:55:53"
const LastCommitLog = "N/A"
const BuildDate = "N/A"
const EOLDate = "2021-12-31 10:10:10"
const EOLDate = "N/A"
const Version = "1.0.0_SNAPSHOT"
const Version = "0.0.1-SNAPSHOT"

View File

@ -33,6 +33,15 @@ export default [
// { path: '/', redirect: '/' },
// ],
// },
{
path: '/cluster/overview_new',
name: 'overview',
component: './Cluster/NewOverview',
// hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
},
{
path: '/cluster/overview',
name: 'overview',
@ -471,6 +480,12 @@ export default [
component: './System/Cluster/Form',
hideInMenu: true
},
{
path: '/system/command',
name: 'command',
component: './System/Command/Index',
hideInMenu: true
},
// {
// path: '/system/settings',
// name: 'settings',

View File

@ -99,6 +99,7 @@ const ConsoleWrapper = ({
<RequestStatusBar
requestInProgress={requestInProgress}
selectedCluster={selectedCluster}
container={consoleRef}
requestResult={
lastDatum
? {

View File

@ -143,6 +143,7 @@ export const RequestStatusBar = ({
requestInProgress,
requestResult,
selectedCluster,
container,
}:Props) => {
let content: React.ReactNode = null;
const clusterContent = (<div className="base-info">
@ -154,11 +155,11 @@ export const RequestStatusBar = ({
</div>
<div className="info-item">
<span></span>
{selectedCluster.host}
<EuiBadge color="default">{selectedCluster.host}</EuiBadge>
</div>
<div className="info-item">
<span></span>
{selectedCluster.version}
<EuiBadge color="default"> {selectedCluster.version}</EuiBadge>
</div>
</div>);
const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
@ -178,6 +179,7 @@ const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
<>
<div className="status_info">
<div className="info-item">
<span></span>
<EuiToolTip
position="top"
content={
@ -193,6 +195,7 @@ const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
</EuiToolTip>
</div>
<div className="info-item">
<span></span>
<EuiToolTip
position="top"
content={
@ -227,7 +230,8 @@ const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
<Drawer title="Request header info"
style={{zIndex:1004}}
width={520}
mask={false}
// getContainer={container.current}
destroyOnClose={true}
visible={headerInfoVisible}
onClose={()=>{setHeaderInfoVisible(false)}}

View File

@ -0,0 +1,33 @@
import * as React from 'react';
import {Tabs} from 'antd';
import Clusters from './components/clusters';
const {TabPane} = Tabs;
const panes = [
{ title: 'Clusters', component: Clusters, key: 'clusters' },
{ title: 'Hosts', component: 'Content of Tab 2', key: 'hosts' },
{title: 'Nodes', component: 'Content of Tab 3',key: 'nodes'},
{title: 'Indices', component: 'Content of Tab 3',key: 'indices'},
];
const NewOverview = ()=>{
return (<div style={{background:'#fff'}} className="overview">
<div>
<Tabs
onChange={()=>{}}
type="card"
tabBarGutter={10}
>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key}>
{typeof pane.component == 'string'? pane.component: <pane.component/>}
</TabPane>
))}
</Tabs>
</div>
</div>);
}
export default NewOverview;

View File

@ -0,0 +1,63 @@
import './cluster_card.scss';
const ClusterCard = ()=>{
return (
<div class="cluster-item">
<div class="cluster-name">
<span>Cluster A</span>
</div>
<div class="cluster-info">
<div class="info-top">
<span class="text">Availability History(Last 7 Days)</span>
</div>
<div class="info-middle">
<span class="label">100.0%</span>
<span class="label label-primary">128 Nodes</span>
<span class="label label-primary">1280 Shards</span>
</div>
<div class="info-bottom">
<span class="status-block bg-green"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-yellow"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-yellow"></span>
<span class="status-block bg-red"></span>
<span class="status-block bg-red"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-yellow"></span>
<span class="status-block bg-red"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-yellow"></span>
<span class="status-block bg-red"></span>
<span class="status-block bg-red"></span>
<span class="status-block bg-green"></span>
<span class="status-block bg-yellow"></span>
<span class="status-block bg-red"></span>
</div>
</div>
<div class="cluster-chart">
<div class="chart-top">
<div class="pie-chart">
<div class="item pie1"></div>
<div class="item pie2"></div>
</div>
<div class="line-chart">
<div class="item line1"></div>
<div class="item line2"></div>
</div>
</div>
<div class="chart-bottom">
<span class="label label-primary">100TB</span>
<span class="label label-primary">Dev</span>
<span class="label label-primary">7.6.1</span>
<span class="label label-primary">8 Nodes</span>
</div>
</div>
</div>
)
}
export default ClusterCard;

View File

@ -0,0 +1,124 @@
.bg-green {
background-color: green;
}
.bg-yellow {
background-color: yellow;
}
.bg-red {
background-color: red;
}
.cluster-item {
border: 1px solid black;
width: 100%;
height: 130px;
margin-bottom: 15px;
display: flex;
}
.cluster-item .cluster-name {
width: 15%;
height: 100%;
background-color: #a9b108;
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
}
.cluster-item .cluster-info {
/*background-color: lightblue;*/
width: 40%;
height: 100%;
padding: 0 5px;
}
.cluster-item .cluster-info .info-top {
/*background-color: lightseagreen;*/
margin: 5px 0 5px 5px;
}
.cluster-item .cluster-info .info-top .text {
height: 23px;
line-height: 23px;
font-weight: bold;
font-size: 12px;
padding-left: 5px
;
}
.cluster-item .cluster-info .info-middle {
width: 100%;
margin: 8px auto;
display: flex;
justify-content: space-between;
}
.cluster-item .label {
text-align: center;
display: inline-block;
padding: 0 10px;
min-width: 60px;
height: 30px;
line-height: 30px;
}
.cluster-item .label.label-primary {
background-color: #1f63af;
color: white;
}
.cluster-item .cluster-info .info-bottom {
display: flex;
flex-wrap: wrap;
margin-top: 3px;
}
.cluster-item .cluster-info .info-bottom .status-block {
width: 18px;
height: 18px;
margin: 3px 5px;
}
.cluster-item .cluster-chart {
width: 45%;
height: 100%;
padding: 0 5px;
;
}
.cluster-item .cluster-chart .chart-top {
height: 90px;
width: 100%;
display: flex;
margin-top: 5px;
margin-bottom: 3px;
}
.cluster-item .cluster-chart .chart-top .pie-chart {
height: 90px;
width: 50%;
display: flex;
justify-content: space-between;
background-color: lightgray;
}
.cluster-item .cluster-chart .chart-top .pie-chart .item {
width: 50%;
}
.cluster-item .cluster-chart .chart-top .pie-chart .item.pie1{
background-color: goldenrod;
}
.cluster-item .cluster-chart .chart-top .pie-chart .item.pie2{
background-color: greenyellow;
}
.cluster-item .cluster-chart .chart-top .line-chart {
height: 90px;
width: 50%;
display: flex;
flex-direction: column;
}
.cluster-item .cluster-chart .chart-top .line-chart .item {
height: 45px;
width: 100%;
}
.cluster-item .cluster-chart .chart-top .line-chart .item.line1{
background-color: #ff8080;
}
.cluster-item .cluster-chart .chart-top .line-chart .item.line2{
background-color: lightseagreen;
}
.cluster-item .cluster-chart .chart-bottom {
height: 30px;
width: 100%;
display: flex;
/*background-color: lightgreen;*/
justify-content: space-between;
}

View File

@ -0,0 +1,32 @@
import {Tabs} from 'antd';
import {Metrics} from './detail';
const {TabPane} = Tabs;
const panes = [
{ title: 'Metrics', component: Metrics, key: 'metrics' },
{ title: 'Infos', component: 'Content of Tab 2', key: 'infos' },
{title: 'Activities', component: 'Content of Tab 3',key: 'activities'},
{title: 'Console', component: 'Content of Tab 3',key: 'console'},
];
const ClusterDetail = ()=>{
return (
<div>
<Tabs
onChange={()=>{}}
type="card"
tabBarGutter={10}
tabPosition="right"
>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key}>
{typeof pane.component == 'string'? pane.component: <pane.component/>}
</TabPane>
))}
</Tabs>
</div>
);
}
export default ClusterDetail;

View File

@ -0,0 +1,73 @@
import { Input, Icon, List, Card,Button } from 'antd';
import * as React from 'react';
import './clusters.scss';
import ClusterDetail from './cluster_detail';
import {TagList} from './tag';
import ClusterCard from './cluster_card';
const { Search } = Input;
const Clusters = ()=>{
const [collapse, setCollapse] = React.useState(false);
const toggleCollapse = ()=>{
setCollapse(!collapse)
}
const clusterList = [1, 2, 3, 4, 5, 6, 7, 8];
return (
<div className="clusters">
<div className="wrapper">
<div className={"col left" + (collapse ? " collapse": "")}>
<div className="search-line">
<div className="search-box">
<Search
placeholder="search"
enterButton="Search"
onSearch={value => console.log(value)}
/>
</div>
<div className="help">
<Button type="link">Get help?</Button>
</div>
</div>
<div className="tag-line">
<TagList value={[{text:"Dev"}, {text:'7.9.2', checked: true}, {text:"Prod"}, {text:"QA"}, {text:"Metrics"}]} />
</div>
<div className="card-cnt">
<List itemLayout="vertical"
size="small"
bordered={false}
pagination={{
onChange: page => {
console.log(page);
},
showSizeChanger: true,
pageSizeOptions: ['5','10','20'],
showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`,
pageSize: 5,
}}
dataSource={clusterList}
renderItem={(item)=>(
<List.Item>
{/* <Card>
<p>Card content</p>
</Card> */}
<ClusterCard/>
</List.Item>
)}
/>
</div>
</div>
<div className="collapse" >
<span className="area" onClick={toggleCollapse}>
<Icon type={collapse ? "right": "left"} className="icon"/>
</span>
</div>
<div className="col right">
<ClusterDetail/>
</div>
</div>
</div>
)
}
export default Clusters;

View File

@ -0,0 +1,72 @@
.overview {
padding: 15px;
.clusters{
>.wrapper{
display: flex;
.col{
flex: 1 1 50%;
position: relative;
&.left{
border-right: 1px solid rgb(232, 232, 232);
padding-right: 15px;
.card-cnt{
margin-top: 10px;
.ant-list-item{
border-bottom: none !important;
}
}
}
&.right{
padding-left: 15px;
}
&.collapse{
flex: 0 0 0px;
max-width: 0px;
min-width: 0px;
width: 0px;
height: 0px;
overflow: hidden;
padding-right: 0;
}
}
>.collapse{
margin: auto;
.area{
font-size: 12px;
display: inline-block;
color: #6c7f90;
z-index: 4;
left: -1px;
width: 22px;
cursor: pointer;
transition: color .3s;
background-color: #fff;
height: 66px;
box-sizing: border-box;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
border: 1px solid #ececec;
border-left: none;
text-align: center;
position: relative;
.icon{
position: absolute;
left: 3px;
top: 25px;
}
}
}
.search-line{
display: flex;
align-items: center;
.search-box{
flex: 1 1 auto;
max-width: 600px;
}
}
.tag-line{
margin: 10px auto;
}
}
}
}

View File

@ -0,0 +1 @@
export * from './metrics';

View File

@ -0,0 +1,8 @@
export const Metrics = ()=>{
return (
<div>
Metrics content
</div>
)
}

View File

@ -0,0 +1,56 @@
import './tag.scss';
import * as React from 'react';
export const Tag = (props={})=>{
const [checked, setChecked] = React.useState(props.checked)
const toggleChecked = ()=>{
if(typeof props.onChange == 'function'){
props.onChange({
checked: !checked,
text: props.text,
})
}
setChecked(!checked);
}
return (
<div className={"tag" +(checked ? ' checked': '')} onClick={toggleChecked}>
<div className="wrapper">
<span className="text">{props.text}</span>
</div>
</div>
)
}
export const TagList = (props)=>{
const [value, setValue] = React.useState(()=>{
return (props.value||[]).filter(item=>item.checked == true)
})
const onTagChange = (citem)=>{
const newVal = [...value]
const idx = newVal.findIndex(item=>item.text == citem.text);
if(idx > -1) {
if(citem.checked == true){
newVal[idx].checked = citem.checked;
}else{
newVal.splice(idx,1)
}
}else{
if(citem.checked == true){
newVal.push(citem);
}
}
if(typeof props.onChange == 'function'){
props.onChange(newVal);
}
setValue(newVal);
}
return (
<div className="tag-list">
{(props.value||[]).map((item)=>{
return <Tag key={item.text} {...item} onChange={onTagChange} />
})}
</div>
)
}

View File

@ -0,0 +1,22 @@
.tag-list{
display: flex;
align-items: center;
.tag{
margin-left: 10px;
display: inline-block;
border: 1px solid #e8e8e8;
cursor: pointer;
&.checked{
background: #1890ff;
color: #fff;
}
>.wrapper{
font-size: 12px;
line-height: 2em;
padding: 0 10px;
}
}
.tag:first-child{
margin-left: 0px;
}
}

View File

@ -0,0 +1,331 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import {Link} from 'umi';
import {
Row,
Col,
Card,
Form,
Input,
Button,
Modal,
message,
Divider,
Drawer,
Tabs,
Descriptions,
Menu,
Table,
Dropdown,
Icon, Popconfirm,
Switch,
} from 'antd';
import Editor from '@monaco-editor/react';
import styles from '../../List/TableList.less';
import {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
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>
)
}
}
@Form.create()
class CreateForm extends React.Component {
okHandle = () => {
const {handleAdd, form} = this.props;
const me = this;
form.validateFields((err, fieldsValue) => {
if (err) return;
fieldsValue['config'] = me.editor.getValue();
handleAdd(fieldsValue);
form.resetFields();
});
};
onEditorDidMount = (editor)=>{
this.editor = editor;
}
render() {
const {modalVisible, form, handleModalVisible} = this.props;
return (
<Modal
destroyOnClose
title="新建索引"
visible={modalVisible}
width={640}
onOk={this.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="索引设置">
<div style={{border: '1px solid rgb(232, 232, 232)'}}>
<Editor
height="300px"
language="json"
theme="light"
options={{
minimap: {
enabled: false,
},
tabSize: 2,
wordBasedSuggestions: true,
}}
onMount={this.onEditorDidMount}
/>
</div>
</FormItem>
</Modal>
);
}
}
/* eslint react/no-multi-comp:0 */
@connect(({ command }) => ({
command
}))
@Form.create()
class Index extends PureComponent {
state = {
modalVisible: false,
updateModalVisible: false,
expandForm: false,
formValues: {},
drawerVisible: false,
editingCommand:{},
indexActiveKey: '1',
showSystemIndices: false,
};
columns = [
{
title: '名称',
dataIndex: 'title',
render: (text, record) => (
<a onClick={()=>{
this.setState({
editingCommand: record,
drawerVisible: true,
});
}}>{text}</a>
)
},
{
title: '标签',
dataIndex: 'tag',
// render: (val)=>{
// return val || 0;
// }
},
{
title: '操作',
render: (text, record) => (
<Fragment>
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.id)}>
<a>删除</a>
</Popconfirm>
</Fragment>
),
},
];
componentDidMount() {
this.fetchData()
}
fetchData = (params={})=>{
const { dispatch } = this.props;
dispatch({
type: 'command/fetchCommandList',
payload: {
...params
}
});
}
handleFormReset = () => {
const { form, dispatch } = this.props;
form.resetFields();
this.setState({
formValues: {},
});
};
handleDeleteClick = (id) => {
const { dispatch } = this.props;
dispatch({
type: 'command/removeCommand',
payload: {
id: id,
}
});
};
handleSearch = e => {
e.preventDefault();
const { dispatch, form } = this.props;
form.validateFields((err, fieldsValue) => {
if (err) return;
this.fetchData({
title: fieldsValue.name,
from: 0,
size: 10,
})
this.setState({
searchKey: fieldsValue.name,
});
});
};
handleModalVisible = flag => {
this.setState({
modalVisible: !!flag,
});
};
handleIndexTabChanged = (activeKey, indexName) => {
}
handleEditorDidMount = (editorName, editor)=>{
this[editorName] = editor;
}
handleIndexSettingsSaveClick = (indexName)=>{
}
buildRawCommonCommandRequest(cmd){
const {requests} = cmd;
if(!requests){
return '';
}
const strReqs = requests.map((req)=>{
const {method, path, body} = req;
return `${method} ${path}\n${body}`;
})
return strReqs.join('\n');
}
handleRereshClick=()=>{
const {searchKey} = this.state;
this.fetchData({
title: searchKey,
})
}
render() {
const {data, total} = this.props.command;
const { modalVisible, updateModalVisible, updateFormValues, drawerVisible, editingCommand } = this.state;
const parentMethods = {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
};
const updateMethods = {
handleUpdateModalVisible: this.handleUpdateModalVisible,
handleUpdate: this.handleUpdate,
};
const {form: { getFieldDecorator }} = this.props;
return (
<PageHeaderWrapper>
<Card bordered={false}>
<div className={styles.tableList}>
<div className={styles.tableListForm}>
<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>
<Col md={8} sm={24} style={{textAlign:"right"}}>
<Button icon="redo" style={{marginRight:10}} onClick={this.handleRereshClick}>刷新</Button>
</Col>
</Row>
</Form>
</div>
<Table bordered
dataSource={data}
rowKey='id'
pagination={
{pageSize: 10,}
}
columns={this.columns}
/>
</div>
</Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} />
<Drawer title={editingCommand.title}
visible={drawerVisible}
onClose={()=>{
this.setState({
drawerVisible: false,
indexActiveKey: '1',
});
}}
width={720}
>
<div style={{border: '1px solid rgb(232, 232, 232)'}}>
<Editor
height="300px"
language="text"
theme="light"
value={this.buildRawCommonCommandRequest(editingCommand)}
options={{
readOnly: true,
minimap: {
enabled: false,
},
tabSize: 2,
wordBasedSuggestions: true,
}}
onMount={(editor)=>this.handleEditorDidMount('indexSettingsEditor', editor)}
/>
</div>
</Drawer>
</PageHeaderWrapper>
);
}
}
export default Index;

View File

@ -0,0 +1,54 @@
import {searchCommand, deleteCommand} from "@/services/command";
import {message} from "antd";
import {formatESSearchResult} from '@/lib/elasticsearch/util';
export default {
namespace: 'command',
state: {
},
effects:{
*fetchCommandList({payload}, {call, put, select}){
let res = yield call(searchCommand, payload);
if(res.error){
message.error(res.error)
return false;
}
res = formatESSearchResult(res)
yield put({
type: 'saveData',
payload: res
})
},
*removeCommand({payload}, {call, put, select}) {
let res = yield call(deleteCommand, payload)
if(res.error){
message.error(res.error)
return false;
}
let {data, total} = yield select(state => state.command);
data = data.filter((item)=>{
return item.id !== payload.id;
})
yield put({
type: 'saveData',
payload: {
data,
total: {
...total,
value: total.value - 1
}
}
})
return res;
},
},
reducers:{
saveData(state, {payload}){
return {
...state,
...payload,
}
}
}
}

View File

@ -0,0 +1,24 @@
import request from '@/utils/request';
import {buildQueryArgs, pathPrefix} from './common';
export async function searchCommand(params) {
let url = `${pathPrefix}/elasticsearch/command`;
let args = buildQueryArgs({
title: params.title,
from: params.from,
size: params.size,
});
if(args.length > 0){
url += args;
}
return request(url, {
method: 'GET'
});
}
export async function deleteCommand(params) {
const url = `${pathPrefix}/elasticsearch/command/${params.id}`;
return request(url, {
method: 'DELETE'
});
}