monitor of node level and index level, common command manage

This commit is contained in:
liugq 2021-11-18 16:26:43 +08:00
parent d65a7f55f8
commit 408c2feb50
27 changed files with 2458 additions and 1517 deletions

View File

@ -12,15 +12,15 @@ import (
"time" "time"
) )
func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (h *APIHandler) HandleAddCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
resBody := map[string]interface{}{ resBody := map[string]interface{}{
} }
reqParams := elastic.CommonCommand{} reqParams := elastic.CommonCommand{}
err := h.DecodeJSON(req, &reqParams) err := h.DecodeJSON(req, &reqParams)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusInternalServerError) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
@ -28,20 +28,25 @@ func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *h
reqParams.ID = util.GetUUID() reqParams.ID = util.GetUUID()
esClient := elastic.GetClient(h.Config.Elasticsearch) esClient := elastic.GetClient(h.Config.Elasticsearch)
queryDSL :=[]byte(fmt.Sprintf(`{"size":1, "query":{"bool":{"must":{"match":{"title":"%s"}}}}}`, reqParams.Title)) queryDSL :=[]byte(fmt.Sprintf(`{"size":1, "query":{"bool":{"must":{"match":{"title.keyword":"%s"}}}}}`, reqParams.Title))
var indexName = orm.GetIndexName(reqParams) var indexName = orm.GetIndexName(reqParams)
searchRes, err := esClient.SearchWithRawQueryDSL(indexName, queryDSL) searchRes, err := esClient.SearchWithRawQueryDSL(indexName, queryDSL)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusInternalServerError) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
if len(searchRes.Hits.Hits) > 0 { if len(searchRes.Hits.Hits) > 0 {
resBody["error"] = "title already exists" resBody["error"] = "title already exists"
h.WriteJSON(w, resBody, http.StatusInternalServerError) h.WriteJSON(w, resBody, http.StatusOK)
return return
} }
_, err = esClient.Index(indexName,"", reqParams.ID, reqParams) _, err = esClient.Index(indexName,"", reqParams.ID, reqParams)
if err != nil {
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
resBody["_id"] = reqParams.ID resBody["_id"] = reqParams.ID
resBody["result"] = "created" resBody["result"] = "created"
@ -49,20 +54,64 @@ func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *h
h.WriteJSON(w, resBody,http.StatusOK) h.WriteJSON(w, resBody,http.StatusOK)
} }
func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
resBody := map[string]interface{}{
}
reqParams := elastic.CommonCommand{}
err := h.DecodeJSON(req, &reqParams)
if err != nil {
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
reqParams.ID = ps.ByName("cid")
esClient := elastic.GetClient(h.Config.Elasticsearch)
queryDSL :=[]byte(fmt.Sprintf(`{"size":1, "query":{"bool":{"must":{"match":{"title.keyword":"%s"}}}}}`, reqParams.Title))
var indexName = orm.GetIndexName(reqParams)
searchRes, err := esClient.SearchWithRawQueryDSL(indexName, queryDSL)
if err != nil {
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
if len(searchRes.Hits.Hits) > 0 && searchRes.Hits.Hits[0].ID.(string) != reqParams.ID {
resBody["error"] = "title already exists"
h.WriteJSON(w, resBody, http.StatusOK)
return
}
_, err = esClient.Index(indexName,"", reqParams.ID, reqParams)
if err != nil {
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
resBody["_id"] = reqParams.ID
resBody["result"] = "updated"
resBody["_source"] = reqParams
h.WriteJSON(w, resBody,http.StatusOK)
}
func (h *APIHandler) HandleQueryCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (h *APIHandler) HandleQueryCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
resBody := map[string]interface{}{ resBody := map[string]interface{}{
} }
var ( var (
title = h.GetParameterOrDefault(req, "title", "") keyword = h.GetParameterOrDefault(req, "keyword", "")
queryDSL = `{"query":{"bool":{"filter":[%s]}}, "size": %d, "from": %d}` queryDSL = `{"query":{"bool":{"must":[%s]}}, "size": %d, "from": %d}`
strSize = h.GetParameterOrDefault(req, "size", "20") strSize = h.GetParameterOrDefault(req, "size", "20")
strFrom = h.GetParameterOrDefault(req, "from", "0") strFrom = h.GetParameterOrDefault(req, "from", "0")
filterBuilder = &strings.Builder{} filterBuilder = &strings.Builder{}
) )
if title != ""{ if keyword != ""{
filterBuilder.WriteString(fmt.Sprintf(`{"prefix":{"title": "%s"}}`, title)) filterBuilder.WriteString(fmt.Sprintf(`{"query_string": {
"default_field": "*",
"query": "%s"
}
}`, keyword))
} }
size, _ := strconv.Atoi(strSize) size, _ := strconv.Atoi(strSize)
if size <= 0 { if size <= 0 {

View File

@ -41,7 +41,8 @@ func Init(cfg *config.AppConfig) {
api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), handler.HandleDeleteIndexAction) api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), handler.HandleDeleteIndexAction)
api.HandleAPIMethod(api.POST, path.Join(esPrefix, "index/:index"), handler.HandleCreateIndexAction) api.HandleAPIMethod(api.POST, path.Join(esPrefix, "index/:index"), handler.HandleCreateIndexAction)
api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleSaveCommonCommandAction) api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleAddCommonCommandAction)
api.HandleAPIMethod(api.PUT, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleSaveCommonCommandAction)
api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction) api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction)
api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleDeleteCommonCommandAction) api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleDeleteCommonCommandAction)

View File

@ -91,6 +91,7 @@ func main() {
orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config") orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config")
orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts") orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts")
orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history") orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history")
orm.RegisterSchema(elastic.CommonCommand{})
alertSrv.GetScheduler().Start() alertSrv.GetScheduler().Start()
},nil){ },nil){
app.Run() app.Run()

View File

@ -1,97 +1,93 @@
export default [ export default [
// user // user
{ {
path: '/user', path: "/user",
component: '../layouts/UserLayout', component: "../layouts/UserLayout",
routes: [ routes: [
{ path: '/user', redirect: '/user/login' }, { path: "/user", redirect: "/user/login" },
{ path: '/user/login', component: './User/Login' }, { path: "/user/login", component: "./User/Login" },
{ path: '/user/register', component: './User/Register' }, { path: "/user/register", component: "./User/Register" },
{ path: '/user/register-result', component: './User/RegisterResult' }, { path: "/user/register-result", component: "./User/RegisterResult" },
], ],
}, },
// app // app
{ {
path: '/', path: "/",
component: '../layouts/BasicLayout', component: "../layouts/BasicLayout",
Routes: ['src/pages/Authorized'], Routes: ["src/pages/Authorized"],
authority: ['admin', 'user'], authority: ["admin", "user"],
routes: [ routes: [
// cluster // cluster
{ path: '/', redirect: '/cluster/overview' }, { path: "/", redirect: "/cluster/overview" },
{ {
path: '/cluster', path: "/cluster",
name: 'cluster', name: "cluster",
icon: 'cluster', icon: "cluster",
routes: [ routes: [
// { path: '/', redirect: '/platform/gateway' }, // { path: '/', redirect: '/platform/gateway' },
// { // {
// path: '/cluster/overview/', // path: '/cluster/overview/',
// name: 'overview', // name: 'overview',
// component: './Cluster/Overview', // component: './Cluster/Overview',
// routes:[ // routes:[
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
// }, // },
{ {
path: '/cluster/overview', path: "/cluster/overview",
name: 'overview', name: "overview",
component: './Cluster/NewOverview', component: "./Cluster/NewOverview",
// hideInMenu: true, // hideInMenu: true,
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, },
], {
}, path: "/cluster/monitoring/:cluster_id",
{ name: "cluster",
path: '/cluster/monitoring/:cluster_id', component: "./Cluster/ClusterMonitor",
name: 'cluster', hideInMenu: true,
component: './Cluster/ClusterMonitor', routes: [{ path: "/", redirect: "/" }],
hideInMenu: true, },
routes:[ {
{ path: '/', redirect: '/' }, path: "/cluster/metrics/",
], name: "monitoring",
}, { component: "./Cluster/Metrics",
path: '/cluster/metrics/', routes: [{ path: "/", redirect: "/" }],
name: 'monitoring', },
component: './Cluster/Metrics', {
routes:[ path: "/cluster/metrics/:cluster_id",
{ path: '/', redirect: '/' }, name: "monitoring",
], component: "./Cluster/Metrics",
}, { hideInMenu: true,
path: '/cluster/metrics/:cluster_id', },
name: 'monitoring', // {
component: './Cluster/Metrics', // path: '/cluster/logs/',
hideInMenu: true, // name: 'logging',
}, // component: './Cluster/SearchMonitor',
// { // routes:[
// path: '/cluster/logs/', // { path: '/', redirect: '/' },
// name: 'logging', // ],
// component: './Cluster/SearchMonitor', // },{
// routes:[ // path: '/cluster/settings/',
// { path: '/', redirect: '/' }, // name: 'settings',
// ], // component: './Cluster/Settings/Base',
// },{ // routes: [
// path: '/cluster/settings/', // {
// name: 'settings', // path: '/cluster/settings',
// component: './Cluster/Settings/Base', // redirect: '/cluster/settings/repository',
// routes: [ // routes:[
// { // { path: '/', redirect: '/' },
// path: '/cluster/settings', // ],
// redirect: '/cluster/settings/repository', // },
// routes:[ // {
// { path: '/', redirect: '/' }, // path: '/cluster/settings/repository',
// ], // component: './Cluster/Settings/Repository',
// }, // routes:[
// { // { path: '/', redirect: '/' },
// path: '/cluster/settings/repository', // ],
// component: './Cluster/Settings/Repository', // }
// routes:[ // ]
// { path: '/', redirect: '/' }, // },
// ], ],
// }
// ]
// },
]
}, },
//devtools //devtools
// { // {
@ -104,40 +100,38 @@ export default [
// component: './DevTool/Console', // component: './DevTool/Console',
// }, // },
//alerting //alerting
{ {
path: '/alerting', path: "/alerting",
name: 'alerting', name: "alerting",
icon: 'alert', icon: "alert",
routes: [{ routes: [
routes:[ {
{ path: '/', redirect: '/' }, routes: [{ path: "/", redirect: "/" }],
], path: "/alerting/overview",
path: '/alerting/overview', component: "./Alerting/pages/Overview/Overview",
component: './Alerting/pages/Overview/Overview', name: "overview",
name: 'overview' },
},{ {
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, path: "/alerting/monitor",
], component: "./Alerting/index",
path: '/alerting/monitor', name: "monitor",
component: './Alerting/index', },
name: 'monitor' {
},{ routes: [{ path: "/", redirect: "/" }],
routes:[ path: "/alerting/destination",
{ path: '/', redirect: '/' }, component: "./Alerting/destination",
], name: "destination",
path: '/alerting/destination', },
component: './Alerting/destination', ],
name: 'destination'
}]
}, },
//data //data
{ {
path: '/data', path: "/data",
name: 'data', name: "data",
icon: 'database', icon: "database",
routes: [ routes: [
// { // {
// path: '/data/overview', // path: '/data/overview',
@ -146,14 +140,12 @@ export default [
// routes:[ // routes:[
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
// }, // },
{ {
path: '/data/index', path: "/data/index",
name: 'index', name: "index",
component: './DataManagement/Index', component: "./DataManagement/Index",
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' },
],
}, },
// { // {
// path: '/data/document', // path: '/data/document',
@ -162,7 +154,7 @@ export default [
// routes:[ // routes:[
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
// }, // },
// { // {
// path: '/data/template', // path: '/data/template',
// name: 'template', // name: 'template',
@ -180,31 +172,25 @@ export default [
// ], // ],
// }, // },
{ {
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, path: "/data/discover",
], name: "discover",
path: '/data/discover', component: "./DataManagement/Discover",
name: 'discover',
component: './DataManagement/Discover',
}, },
{ {
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, path: "/data/views/",
], name: "indexPatterns",
path: '/data/views/', component: "./DataManagement/IndexPatterns",
name: 'indexPatterns',
component: './DataManagement/IndexPatterns',
}, },
] ],
}, },
//search //search
{ {
path: '/search', path: "/search",
name: 'search', name: "search",
icon: 'search', icon: "search",
routes: [ routes: [
// { // {
// path: '/search/overview', // path: '/search/overview',
@ -245,34 +231,30 @@ export default [
// ], // ],
// }, // },
// ] // ]
// }, // },
{ {
path: '/search/alias', path: "/search/alias",
name: 'alias', name: "alias",
component: './SearchManage/alias/Alias', component: "./SearchManage/alias/Alias",
routes: [ routes: [
{ {
path: '/search/alias', path: "/search/alias",
redirect: '/search/alias/index', redirect: "/search/alias/index",
// routes:[ // routes:[
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
}, },
{ {
path: '/search/alias/index', path: "/search/alias/index",
component: './SearchManage/alias/AliasManage', component: "./SearchManage/alias/AliasManage",
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, },
], {
}, path: "/search/alias/rule",
{ component: "./SearchManage/alias/Rule",
path: '/search/alias/rule', routes: [{ path: "/", redirect: "/" }],
component: './SearchManage/alias/Rule', },
routes:[ ],
{ path: '/', redirect: '/' },
],
}
]
}, },
// { // {
// path: '/search/dict', // path: '/search/dict',
@ -328,33 +310,33 @@ export default [
// ] // ]
// } // }
//, { //, {
// path: '/search/nlp', // path: '/search/nlp',
// name: 'nlp', // name: 'nlp',
// component: './SearchManage/nlp/NLP', // component: './SearchManage/nlp/NLP',
// routes: [ // routes: [
// { // {
// path: '/search/nlp', // path: '/search/nlp',
// redirect: '/search/nlp/query', // redirect: '/search/nlp/query',
// }, // },
// { // {
// path: '/search/nlp/query', // path: '/search/nlp/query',
// component: './SearchManage/nlp/Query', // component: './SearchManage/nlp/Query',
// }, // },
// { // {
// path: '/search/nlp/intention', // path: '/search/nlp/intention',
// component: './SearchManage/nlp/Intention', // component: './SearchManage/nlp/Intention',
// }, // },
// { // {
// path: '/search/nlp/knowledge', // path: '/search/nlp/knowledge',
// component: './SearchManage/nlp/Knowledge', // component: './SearchManage/nlp/Knowledge',
// }, // },
// { // {
// path: '/search/nlp/text', // path: '/search/nlp/text',
// component: './SearchManage/nlp/Text', // component: './SearchManage/nlp/Text',
// } // }
//] //]
//}, //},
] ],
}, },
// //
// //sync // //sync
@ -450,32 +432,32 @@ export default [
//settings //settings
{ {
path: '/system', path: "/system",
name: 'system', name: "system",
icon: 'setting', icon: "setting",
routes: [ routes: [
{ {
path: '/system/cluster', path: "/system/cluster",
name: 'cluster', name: "cluster",
component: './System/Cluster/Index', component: "./System/Cluster/Index",
}, },
{ {
path: '/system/cluster/regist', path: "/system/cluster/regist",
name: 'registCluster', name: "registCluster",
component: './System/Cluster/Step', component: "./System/Cluster/Step",
hideInMenu: true hideInMenu: true,
}, },
{ {
path: '/system/cluster/edit', path: "/system/cluster/edit",
name: 'editCluster', name: "editCluster",
component: './System/Cluster/Form', component: "./System/Cluster/Form",
hideInMenu: true hideInMenu: true,
}, },
{ {
path: '/system/command', path: "/system/command",
name: 'commonCommand', name: "commonCommand",
component: './System/Command/Index', component: "./System/Command/Index",
hideInMenu: true // hideInMenu: true
}, },
// { // {
// path: '/system/settings', // path: '/system/settings',
@ -549,7 +531,7 @@ export default [
// }, // },
// ] // ]
// }, // },
] ],
}, },
// // forms // // forms
@ -768,7 +750,7 @@ export default [
// ], // ],
// }, // },
{ {
component: '404', component: "404",
}, },
], ],
}, },

View File

@ -1,59 +1,71 @@
import { Button, Dropdown, List, Spin, message, Icon, Input } from 'antd'; import { Button, Dropdown, List, Spin, message, Icon, Input } from "antd";
import React from 'react'; import React from "react";
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from "react-infinite-scroller";
import styles from './DropdownSelect.less'; import styles from "./DropdownSelect.less";
import _ from "lodash"; import _ from "lodash";
import {DropdownItem} from './DropdownItem'; import { DropdownItem } from "./DropdownItem";
import {HealthStatusCircle} from '@/components/infini/health_status_circle' import { HealthStatusCircle } from "@/components/infini/health_status_circle";
class DropdownSelect extends React.Component{ class DropdownSelect extends React.Component {
constructor(props){ constructor(props) {
super(props) super(props);
this.state={ this.state = {
value: props.defaultValue, value: props.defaultValue,
loading: false, loading: false,
hasMore: props.data.length > props.size, hasMore: props.data.length > props.size,
overlayVisible: false, overlayVisible: false,
data: (props.data || []).slice(0, props.size), data: (props.data || []).slice(0, props.size),
dataSource: [...props.data], dataSource: [...props.data],
};
}
handleItemClick = (item) => {
let preValue = this.props.value || this.state.value;
this.setState(
{
value: item,
overlayVisible: false,
},
() => {
let onChange = this.props.onChange;
if (preValue != item && onChange && typeof onChange == "function") {
onChange(item);
}
}
);
};
componentDidMount() {}
componentDidUpdate(preProps, preState) {
if (preProps.data.length != preState.dataSource.length) {
const newData = [...preProps.data];
this.setState({
dataSource: newData,
data: newData,
hasMore: newData.length > this.props.size,
selectedIndex: -1,
});
} }
} }
handleItemClick = (item)=>{
let preValue = this.props.value || this.state.value;
this.setState({
value: item,
overlayVisible: false,
},()=>{
let onChange = this.props.onChange;
if(preValue != item && onChange && typeof onChange == 'function'){
onChange(item)
}
})
}
componentDidMount(){
}
handleInfiniteOnLoad = (current) => { handleInfiniteOnLoad = (current) => {
let {size } = this.props; let { size } = this.props;
let targetLength = current * size; let targetLength = current * size;
let {hasMore, dataSource} = this.state; let { hasMore, dataSource } = this.state;
if(dataSource.length < targetLength){ if (dataSource.length < targetLength) {
targetLength = dataSource.length; targetLength = dataSource.length;
hasMore = false hasMore = false;
} }
const newData = this.state.dataSource.slice(0, targetLength); const newData = this.state.dataSource.slice(0, targetLength);
this.setState({ this.setState({
data: newData, data: newData,
hasMore: hasMore, hasMore: hasMore,
}) });
} };
handleInputChange = (e) =>{ handleInputChange = (e) => {
const name = e.target.value; const name = e.target.value;
const newData = this.props.data.filter(item=>{ const newData = this.props.data.filter((item) => {
return item.name.includes(name); return item.name.includes(name);
}); });
this.setState({ this.setState({
@ -61,76 +73,127 @@ class DropdownSelect extends React.Component{
dataSource: newData, dataSource: newData,
data: newData, data: newData,
hasMore: newData.length > this.props.size, hasMore: newData.length > this.props.size,
}) });
} };
render() {
render(){
let me = this; let me = this;
const {labelField, clusterStatus} = this.props; const { labelField, clusterStatus } = this.props;
let value = this.props.value || this.state.value; let value = this.props.value || this.state.value;
let displayVaue = value[labelField]; let displayVaue = value[labelField];
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}> const menu = (
<div className={styles.infiniteContainer} style={{height: this.props.height}}> <div className={styles.dropmenu} style={{ width: this.props.width }}>
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}> <div
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} /> className={styles.infiniteContainer}
</div> style={{ height: this.props.height }}
<InfiniteScroll >
<div
className={styles.filter}
style={{ paddingTop: 10, paddingBottom: 0 }}
>
<input
className={styles["btn-ds"]}
style={{ outline: "none" }}
onChange={this.handleInputChange}
placeholder="输入集群名称查找"
value={this.state.displayValue || ""}
/>
</div>
<InfiniteScroll
initialLoad={false} initialLoad={false}
loadMore={this.handleInfiniteOnLoad} loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore} hasMore={!this.state.loading && this.state.hasMore}
useWindow={false} useWindow={false}
> >
<div className={styles.dslist}> <div className={styles.dslist}>
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>} {(!this.state.data || !this.state.data.length) && (
{(this.state.data || []).map((item)=>{ <div
// return <div className={styles.item}> style={{
// <Button key={item[labelField]} display: "flex",
// onClick={() => { justifyContent: "center",
// this.handleItemClick(item) alignItems: "center",
// }} height: 50,
// className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button> }}
// </div> >
const cstatus = clusterStatus ? clusterStatus[item.id] : null; 匹配不到集群(匹配规则为前缀匹配)
return <DropdownItem key={item.id} </div>
isSelected={item.id===value.id} )}
clusterItem={item} {(this.state.data || []).map((item) => {
clusterStatus={cstatus} // return <div className={styles.item}>
onClick={() => { // <Button key={item[labelField]}
this.handleItemClick(item) // onClick={() => {
}} // this.handleItemClick(item)
/> // }}
})} // className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
// </div>
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return (
<DropdownItem
key={item.id}
isSelected={item.id === value.id}
clusterItem={item}
clusterStatus={cstatus}
onClick={() => {
this.handleItemClick(item);
}}
/>
);
})}
</div>
</InfiniteScroll>
</div> </div>
</InfiniteScroll> {!this.state.loading && this.state.hasMore && (
<div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
pull load more
</div>
)}
</div> </div>
{!this.state.loading && this.state.hasMore && ( );
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}>
pull load more
</div>
)}
</div>);
const cstatus = clusterStatus ? clusterStatus[value?.id] : null; const cstatus = clusterStatus ? clusterStatus[value?.id] : null;
return( return this.props.visible ? (
this.props.visible ? <Dropdown
(<Dropdown overlay={menu} placement="bottomLeft" visible={this.state.overlayVisible} overlay={menu}
onVisibleChange={(flag)=>{ placement="bottomLeft"
this.setState({ overlayVisible: flag }); visible={this.state.overlayVisible}
}}> onVisibleChange={(flag) => {
{/* <Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}} this.setState({ overlayVisible: flag });
}}
>
{/* <Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}}
type="caret-down"/></Button> */} type="caret-down"/></Button> */}
<span style={{position:'relative'}}> <span style={{ position: "relative" }}>
<i style={{position: 'absolute', left:15,zIndex:10, top: -28}}> <i style={{ position: "absolute", left: 15, zIndex: 10, top: -28 }}>
{cstatus?.available ? <HealthStatusCircle status={cstatus?.health?.status} />: <Icon type='close-circle' style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}} />} {cstatus?.available ? (
</i> <HealthStatusCircle status={cstatus?.health?.status} />
<input className={styles['btn-ds']} style={{outline:'none', paddingLeft:22}} value={value[labelField]} readOnly={true} /> ) : (
<Icon style={{position:'absolute', top:-6, right:-4}} type="caret-down"/> <Icon
</span> type="close-circle"
style={{
</Dropdown>) : "" width: 14,
) height: 14,
color: "red",
borderRadius: 14,
boxShadow: "0px 0px 5px #555",
}}
/>
)}
</i>
<input
className={styles["btn-ds"]}
style={{ outline: "none", paddingLeft: 22 }}
value={value[labelField]}
readOnly={true}
/>
<Icon
style={{ position: "absolute", top: -6, right: -4 }}
type="caret-down"
/>
</span>
</Dropdown>
) : (
""
);
} }
} }
export default DropdownSelect; export default DropdownSelect;

View File

@ -1,24 +1,24 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from "react";
import { FormattedMessage, formatMessage } from 'umi/locale'; import { FormattedMessage, formatMessage } from "umi/locale";
import { Spin, Tag, Menu, Icon, Dropdown, Avatar, Tooltip } from 'antd'; import { Spin, Tag, Menu, Icon, Dropdown, Avatar, Tooltip } from "antd";
import moment from 'moment'; import moment from "moment";
import groupBy from 'lodash/groupBy'; import groupBy from "lodash/groupBy";
import NoticeIcon from '../NoticeIcon'; import NoticeIcon from "../NoticeIcon";
import HeaderSearch from '../HeaderSearch'; import HeaderSearch from "../HeaderSearch";
import SelectLang from '../SelectLang'; import SelectLang from "../SelectLang";
import styles from './index.less'; import styles from "./index.less";
import {ConsoleUI} from '@/pages/DevTool/Console'; import { ConsoleUI } from "@/pages/DevTool/Console";
import { Resizable } from "re-resizable"; import { Resizable } from "re-resizable";
import {ResizeBar} from '@/components/infini/resize_bar'; import { ResizeBar } from "@/components/infini/resize_bar";
export default class GlobalHeaderRight extends PureComponent { export default class GlobalHeaderRight extends PureComponent {
state={consoleVisible: false} state = { consoleVisible: false };
getNoticeData() { getNoticeData() {
const { notices = [] } = this.props; const { notices = [] } = this.props;
if (notices.length === 0) { if (notices.length === 0) {
return {}; return {};
} }
const newNotices = notices.map(notice => { const newNotices = notices.map((notice) => {
const newNotice = { ...notice }; const newNotice = { ...notice };
if (newNotice.datetime) { if (newNotice.datetime) {
newNotice.datetime = moment(notice.datetime).fromNow(); newNotice.datetime = moment(notice.datetime).fromNow();
@ -28,10 +28,10 @@ export default class GlobalHeaderRight extends PureComponent {
} }
if (newNotice.extra && newNotice.status) { if (newNotice.extra && newNotice.status) {
const color = { const color = {
todo: '', todo: "",
processing: 'blue', processing: "blue",
urgent: 'red', urgent: "red",
doing: 'gold', doing: "gold",
}[newNotice.status]; }[newNotice.status];
newNotice.extra = ( newNotice.extra = (
<Tag color={color} style={{ marginRight: 0 }}> <Tag color={color} style={{ marginRight: 0 }}>
@ -41,7 +41,7 @@ export default class GlobalHeaderRight extends PureComponent {
} }
return newNotice; return newNotice;
}); });
return groupBy(newNotices, 'type'); return groupBy(newNotices, "type");
} }
render() { render() {
@ -57,15 +57,24 @@ export default class GlobalHeaderRight extends PureComponent {
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}> <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
<Menu.Item key="userCenter"> <Menu.Item key="userCenter">
<Icon type="user" /> <Icon type="user" />
<FormattedMessage id="menu.account.center" defaultMessage="account center" /> <FormattedMessage
id="menu.account.center"
defaultMessage="account center"
/>
</Menu.Item> </Menu.Item>
<Menu.Item key="userinfo"> <Menu.Item key="userinfo">
<Icon type="setting" /> <Icon type="setting" />
<FormattedMessage id="menu.account.settings" defaultMessage="account settings" /> <FormattedMessage
id="menu.account.settings"
defaultMessage="account settings"
/>
</Menu.Item> </Menu.Item>
<Menu.Item key="triggerError"> <Menu.Item key="triggerError">
<Icon type="close-circle" /> <Icon type="close-circle" />
<FormattedMessage id="menu.account.trigger" defaultMessage="Trigger Error" /> <FormattedMessage
id="menu.account.trigger"
defaultMessage="Trigger Error"
/>
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item key="logout"> <Menu.Item key="logout">
@ -76,7 +85,7 @@ export default class GlobalHeaderRight extends PureComponent {
); );
const noticeData = this.getNoticeData(); const noticeData = this.getNoticeData();
let className = styles.right; let className = styles.right;
if (theme === 'dark') { if (theme === "dark") {
className = `${styles.right} ${styles.dark}`; className = `${styles.right} ${styles.dark}`;
} }
return ( return (
@ -97,13 +106,19 @@ export default class GlobalHeaderRight extends PureComponent {
}} }}
/> */} /> */}
<a className={styles.action} onClick={()=>{ <a
const {history, selectedCluster} = this.props; className={styles.action}
// history.push(`/dev_tool`); onClick={() => {
this.setState({ const { history, selectedCluster } = this.props;
consoleVisible: !this.state.consoleVisible // history.push(`/dev_tool`);
}) this.setState({
}}> <Icon type="code" /></a> consoleVisible: !this.state.consoleVisible,
});
}}
>
{" "}
<Icon type="code" />
</a>
{/* <NoticeIcon {/* <NoticeIcon
className={styles.action} className={styles.action}
@ -158,17 +173,19 @@ export default class GlobalHeaderRight extends PureComponent {
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} /> <Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
)} */} )} */}
<SelectLang className={styles.action} /> <SelectLang className={styles.action} />
<div style={{ <div
display: this.state.consoleVisible ? 'block': 'none', style={{
borderTop: "solid 1px #ddd", display: this.state.consoleVisible ? "block" : "none",
background: "#fff", borderTop: "solid 1px #ddd",
position: "fixed", background: "#fff",
left: 0, position: "fixed",
right: 0, left: 0,
bottom: 0, right: 0,
zIndex: 1002, bottom: 0,
}}> zIndex: 1002,
{/* <Resizable }}
>
{/* <Resizable
defaultSize={{ defaultSize={{
height: '50vh' height: '50vh'
}} }}
@ -185,19 +202,23 @@ export default class GlobalHeaderRight extends PureComponent {
bottomLeft: false, bottomLeft: false,
topLeft: false, topLeft: false,
}}> */} }}> */}
{this.props.clusterList.length > 0 && <ConsoleUI selectedCluster={this.props.selectedCluster} {this.props.clusterList.length > 0 &&
clusterList={this.props.clusterList} this.props.selectedCluster.id != "" && (
visible={false} <ConsoleUI
minimize={true} selectedCluster={this.props.selectedCluster}
onMinimizeClick={()=>{ clusterList={this.props.clusterList}
this.setState({ visible={false}
consoleVisible: false, minimize={true}
}) onMinimizeClick={() => {
}} this.setState({
clusterStatus={this.props.clusterStatus} consoleVisible: false,
resizeable={true} });
/>} }}
</div> clusterStatus={this.props.clusterStatus}
resizeable={true}
/>
)}
</div>
</div> </div>
); );
} }
@ -205,4 +226,4 @@ export default class GlobalHeaderRight extends PureComponent {
const TopHandle = () => { const TopHandle = () => {
return <div style={{ background: "red" }}>hello world</div>; return <div style={{ background: "red" }}>hello world</div>;
}; };

View File

@ -1,16 +1,16 @@
// @ts-ignore // @ts-ignore
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from "react";
import { Modal, Form, Input, Tag } from 'antd'; import { Modal, Form, Input, Tag } from "antd";
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from "@ant-design/icons";
interface ITagGeneratorProps { interface ITagGeneratorProps {
value?: Array<string>, value?: Array<string>;
onChange?: (val: Array<string>) => void; onChange?: (val: Array<string>) => void;
} }
const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => { export const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
const [inputVisible, setInputVisible] = useState<boolean>(false); const [inputVisible, setInputVisible] = useState<boolean>(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState("");
const handleInputChange = (e) => { const handleInputChange = (e) => {
setInputValue(e.target.value); setInputValue(e.target.value);
@ -20,31 +20,52 @@ const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
setInputVisible(true); setInputVisible(true);
}; };
const handleInputConfirm = useCallback((e) => { const handleInputConfirm = useCallback(
onChange([...(value || []), e.target.value]); (e) => {
setInputVisible(false); onChange([...(value || []), e.target.value]);
setInputValue(''); setInputVisible(false);
}, [value]); setInputValue("");
},
[value]
);
const handleRemove = useCallback((index) => { const handleRemove = useCallback(
const newValue = [...value]; (index) => {
newValue.splice(index, 1); const newValue = [...value];
onChange(newValue); newValue.splice(index, 1);
}, [value]); onChange(newValue);
},
[value]
);
return ( return (
<div> <div>
{value.map((tag, index) => ( {value.map((tag, index) => (
<Tag key={index} closable style={{ padding: '4px 6px', fontSize: 16, margin: '0 10px 10px 0' }} onClose={() => handleRemove(index)}>{tag}</Tag> <Tag
key={index}
closable
style={{ padding: "4px 6px", fontSize: 16, margin: "5px 10px 5px 0" }}
onClose={() => handleRemove(index)}
>
{tag}
</Tag>
))} ))}
{inputVisible && <Input value={inputValue} onChange={handleInputChange} style={{ width: 100 }} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm} />} {inputVisible && (
<Input
value={inputValue}
onChange={handleInputChange}
style={{ width: 100 }}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
)}
{!inputVisible && ( {!inputVisible && (
<Tag onClick={showInput} style={{ padding: '4px 6px', fontSize: 16 }}> <Tag onClick={showInput} style={{ padding: "4px 6px", fontSize: 16 }}>
<PlusOutlined /> <PlusOutlined />
</Tag> </Tag>
)} )}
</div> </div>
) );
}; };
interface ICommonCommandModalProps { interface ICommonCommandModalProps {
@ -60,21 +81,27 @@ const CommonCommandModal = Form.create()((props: ICommonCommandModalProps) => {
try { try {
const values = await form.validateFields(); const values = await form.validateFields();
props.onConfirm(values); props.onConfirm(values);
} catch (e) { } catch (e) {}
}
}; };
return ( return (
<Modal title="保存常用命令" visible={true} onCancel={props.onClose} onOk={handleConfirm} zIndex={1003} cancelText="取消" okText="确认"> <Modal
title="保存常用命令"
visible={true}
onCancel={props.onClose}
onOk={handleConfirm}
zIndex={1003}
cancelText="取消"
okText="确认"
>
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="标题"> <Form.Item label="标题">
{form.getFieldDecorator('title', { {form.getFieldDecorator("title", {
rules: [{ required: true, message: '请输入标题' }] rules: [{ required: true, message: "请输入标题" }],
})(<Input />)} })(<Input />)}
</Form.Item> </Form.Item>
<Form.Item label="标签"> <Form.Item label="标签">
{form.getFieldDecorator('tag')( <TagGenerator />)} {form.getFieldDecorator("tag")(<TagGenerator />)}
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>

View File

@ -1,32 +1,38 @@
// @ts-ignore // @ts-ignore
import React, { useRef, useEffect, CSSProperties, useMemo } from 'react'; import React, { useRef, useEffect, CSSProperties, useMemo } from "react";
import ace from 'brace'; import ace from "brace";
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip, PropertySortType } from '@elastic/eui'; import {
import { SenseEditor } from '../entities/sense_editor'; EuiFlexGroup,
import { LegacyCoreEditor } from '../modules/legacy_core_editor/legacy_core_editor'; EuiFlexItem,
import ConsoleMenu from './ConsoleMenu'; EuiIcon,
EuiToolTip,
PropertySortType,
} from "@elastic/eui";
import { SenseEditor } from "../entities/sense_editor";
import { LegacyCoreEditor } from "../modules/legacy_core_editor/legacy_core_editor";
import ConsoleMenu from "./ConsoleMenu";
// import { RequestContextProvider } from '../contexts/request_context'; // import { RequestContextProvider } from '../contexts/request_context';
import { getDocumentation, autoIndent } from '../entities/console_menu_actions'; import { getDocumentation, autoIndent } from "../entities/console_menu_actions";
import './ConsoleInput.scss'; import "./ConsoleInput.scss";
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es'; import { useSendCurrentRequestToES } from "../hooks/use_send_current_request_to_es";
import { useSetInputEditor } from '../hooks/use_set_input_editor'; import { useSetInputEditor } from "../hooks/use_set_input_editor";
import '@elastic/eui/dist/eui_theme_light.css'; import "@elastic/eui/dist/eui_theme_light.css";
import { instance as registry } from '../contexts/editor_context/editor_registry'; import { instance as registry } from "../contexts/editor_context/editor_registry";
import 'antd/dist/antd.css'; import "antd/dist/antd.css";
import {retrieveAutoCompleteInfo} from '../modules/mappings/mappings'; import { retrieveAutoCompleteInfo } from "../modules/mappings/mappings";
import {useSaveCurrentTextObject} from '../hooks/use_save_current_text_object'; import { useSaveCurrentTextObject } from "../hooks/use_save_current_text_object";
import {useEditorReadContext} from '../contexts/editor_context/editor_context'; import { useEditorReadContext } from "../contexts/editor_context/editor_context";
import {useDataInit} from '../hooks/use_data_init'; import { useDataInit } from "../hooks/use_data_init";
import { useServicesContext } from '../contexts'; import { useServicesContext } from "../contexts";
import {applyCurrentSettings} from './apply_editor_settings'; import { applyCurrentSettings } from "./apply_editor_settings";
import { subscribeResizeChecker } from './subscribe_console_resize_checker'; import { subscribeResizeChecker } from "./subscribe_console_resize_checker";
const abs: CSSProperties = { const abs: CSSProperties = {
position: 'absolute', position: "absolute",
top: '0', top: "0",
left: '0', left: "0",
bottom: '0', bottom: "0",
right: '0', right: "0",
}; };
// interface IConsoleInputProps { // interface IConsoleInputProps {
@ -41,19 +47,16 @@ const SendRequestButton = (props: any) => {
const sendCurrentRequestToES = useSendCurrentRequestToES(); const sendCurrentRequestToES = useSendCurrentRequestToES();
const saveCurrentTextObject = useSaveCurrentTextObject(); const saveCurrentTextObject = useSaveCurrentTextObject();
const {saveCurrentTextObjectRef} = props; const { saveCurrentTextObjectRef } = props;
useEffect(()=>{ useEffect(() => {
saveCurrentTextObjectRef.current = saveCurrentTextObject saveCurrentTextObjectRef.current = saveCurrentTextObject;
}, [saveCurrentTextObjectRef]) }, [saveCurrentTextObjectRef]);
return ( return (
<EuiToolTip <EuiToolTip content={"点击发送请求"}>
content={'点击发送请求'}
>
<button <button
data-test-subj="sendRequestButton" data-test-subj="sendRequestButton"
aria-label={'Click to send request'} aria-label={"Click to send request"}
className="conApp__editorActionButton conApp__editorActionButton--success" className="conApp__editorActionButton conApp__editorActionButton--success"
onClick={sendCurrentRequestToES} onClick={sendCurrentRequestToES}
> >
@ -64,11 +67,11 @@ const SendRequestButton = (props: any) => {
}; };
interface ConsoleInputProps { interface ConsoleInputProps {
clusterID: string, clusterID: string;
initialText: string | undefined, initialText: string | undefined;
saveEditorContent: (content: string)=>void, saveEditorContent: (content: string) => void;
paneKey: string, paneKey: string;
height?: string, height?: string;
} }
const DEFAULT_INPUT_VALUE = `GET _search const DEFAULT_INPUT_VALUE = `GET _search
@ -78,46 +81,59 @@ const DEFAULT_INPUT_VALUE = `GET _search
} }
}`; }`;
const ConsoleInputUI = ({
const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, height='100%'}:ConsoleInputProps) => { clusterID,
initialText,
saveEditorContent,
paneKey,
height = "100%",
}: ConsoleInputProps) => {
const editorRef = useRef<HTMLDivElement | null>(null); const editorRef = useRef<HTMLDivElement | null>(null);
const editorActionsRef = useRef<HTMLDivElement | null>(null); const editorActionsRef = useRef<HTMLDivElement | null>(null);
const editorInstanceRef = useRef<SenseEditor | null>(null); const editorInstanceRef = useRef<SenseEditor | null>(null);
const setInputEditor = useSetInputEditor(); const setInputEditor = useSetInputEditor();
const consoleMenuRef = useRef<ConsoleMenu | null>(null); const consoleMenuRef = useRef<ConsoleMenu | null>(null);
const aceEditorRef = useRef<ace.Editor | null>(null); const aceEditorRef = useRef<ace.Editor | null>(null);
const sendCurrentRequestToESRef = useRef(()=>{}); const sendCurrentRequestToESRef = useRef(() => {});
const saveCurrentTextObjectRef = useRef((content:string)=>{}); const saveCurrentTextObjectRef = useRef((content: string) => {});
sendCurrentRequestToESRef.current = useSendCurrentRequestToES(); sendCurrentRequestToESRef.current = useSendCurrentRequestToES();
const {services:{settings}} = useServicesContext(); const {
services: { settings },
} = useServicesContext();
useEffect(() => { useEffect(() => {
const aceEditor = ace.edit(editorRef.current!); const aceEditor = ace.edit(editorRef.current!);
aceEditorRef.current = aceEditor; aceEditorRef.current = aceEditor;
const legacyCoreEditor = new LegacyCoreEditor(aceEditor, editorActionsRef.current as HTMLElement); const legacyCoreEditor = new LegacyCoreEditor(
aceEditor,
editorActionsRef.current as HTMLElement
);
aceEditor.commands.addCommand({ aceEditor.commands.addCommand({
name: 'exec_request', name: "exec_request",
bindKey: {win: "Ctrl-enter", mac: "Command-enter|Ctrl-enter"}, bindKey: { win: "Ctrl-enter", mac: "Command-enter|Ctrl-enter" },
exec: ()=>{ exec: () => {
sendCurrentRequestToESRef.current(); sendCurrentRequestToESRef.current();
} },
}) });
const senseEditor = new SenseEditor(legacyCoreEditor); const senseEditor = new SenseEditor(legacyCoreEditor);
// senseEditor.highlightCurrentRequestsAndUpdateActionBar(); // senseEditor.highlightCurrentRequestsAndUpdateActionBar();
editorInstanceRef.current = senseEditor; editorInstanceRef.current = senseEditor;
setInputEditor(senseEditor); setInputEditor(senseEditor);
senseEditor.paneKey = paneKey; senseEditor.paneKey = paneKey;
senseEditor.update(initialText || DEFAULT_INPUT_VALUE); senseEditor.update(initialText || DEFAULT_INPUT_VALUE);
applyCurrentSettings(senseEditor!.getCoreEditor(), {fontSize:12, wrapMode: true,}); applyCurrentSettings(senseEditor!.getCoreEditor(), {
fontSize: 12,
wrapMode: true,
});
function setupAutosave() { function setupAutosave() {
let timer: number; let timer: number;
const saveDelay = 500; const saveDelay = 500;
senseEditor.getCoreEditor().on('change', () => { senseEditor.getCoreEditor().on("change", () => {
if (timer) { if (timer) {
clearTimeout(timer); clearTimeout(timer);
} }
@ -129,14 +145,17 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
try { try {
const content = senseEditor.getCoreEditor().getValue(); const content = senseEditor.getCoreEditor().getValue();
// saveCurrentTextObjectRef.current(content); // saveCurrentTextObjectRef.current(content);
saveEditorContent(content) saveEditorContent(content);
} catch (e) { } catch (e) {
console.log(e) console.log(e);
// Ignoring saving error // Ignoring saving error
} }
} }
const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, senseEditor); const unsubscribeResizer = subscribeResizeChecker(
editorRef.current!,
senseEditor
);
setupAutosave(); setupAutosave();
return () => { return () => {
@ -144,68 +163,76 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
if (editorInstanceRef.current) { if (editorInstanceRef.current) {
editorInstanceRef.current.getCoreEditor().destroy(); editorInstanceRef.current.getCoreEditor().destroy();
} }
} };
}, []); }, []);
useEffect(()=>{ useEffect(() => {
retrieveAutoCompleteInfo(settings, settings.getAutocomplete(), clusterID); retrieveAutoCompleteInfo(settings, settings.getAutocomplete(), clusterID);
aceEditorRef.current && (aceEditorRef.current['clusterID'] = clusterID); aceEditorRef.current && (aceEditorRef.current["clusterID"] = clusterID);
},[clusterID]) }, [clusterID]);
const handleSaveAsCommonCommand = async () => { const handleSaveAsCommonCommand = async () => {
const editor = editorInstanceRef.current; const editor = editorInstanceRef.current;
if(editor == null){ if (editor == null) {
console.warn('editor is null') console.warn("editor is null");
return return;
} }
const requests = await editor.getRequestsInRange(); const requests = await editor.getRequestsInRange();
const formattedRequest = requests.map(request => ({ const formattedRequest = requests.map((request) => ({
method: request.method, method: request.method,
path: request.url, path: request.url,
body: (request.data || []).join('\n'), body: (request.data || []).join("\n"),
})); }));
return formattedRequest; return formattedRequest;
}; };
return ( return (
<div
<div style={{...abs, height: height}} data-test-subj="console-application" className="conApp"> style={{ ...abs, height: height }}
<div className="conApp__editor"> data-test-subj="console-application"
<ul className="conApp__autoComplete" id="autocomplete" /> className="conApp"
<EuiFlexGroup >
className="conApp__editorActions" <div className="conApp__editor">
id="ConAppEditorActions" <ul className="conApp__autoComplete" id="autocomplete" />
gutterSize="none" <EuiFlexGroup
responsive={false} className="conApp__editorActions"
ref={editorActionsRef} id="ConAppEditorActions"
> gutterSize="none"
<EuiFlexItem> responsive={false}
<SendRequestButton saveCurrentTextObjectRef={saveCurrentTextObjectRef}/> ref={editorActionsRef}
</EuiFlexItem> >
<EuiFlexItem> <EuiFlexItem>
<ConsoleMenu <SendRequestButton
getCurl={() => { saveCurrentTextObjectRef={saveCurrentTextObjectRef}
return editorInstanceRef.current!.getRequestsAsCURL('');
}}
getDocumentation={() => {
return getDocumentation(editorInstanceRef.current!, '');
}}
autoIndent={(event) => {
autoIndent(editorInstanceRef.current!, event);
}}
saveAsCommonCommand={handleSaveAsCommonCommand}
ref={consoleMenuRef}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div
ref={editorRef}
id={`Editor_${editorInstanceRef.current?.paneKey}`}
className="conApp__editorContent"
data-test-subj="request-editor"
onClick={()=>{consoleMenuRef.current?.closePopover(); aceEditorRef.current?.focus()}}
/> />
</div> </EuiFlexItem>
</div> <EuiFlexItem>
<ConsoleMenu
getCurl={() => {
return editorInstanceRef.current!.getRequestsAsCURL("");
}}
getDocumentation={() => {
return getDocumentation(editorInstanceRef.current!, "");
}}
autoIndent={(event) => {
autoIndent(editorInstanceRef.current!, event);
}}
saveAsCommonCommand={handleSaveAsCommonCommand}
ref={consoleMenuRef}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div
ref={editorRef}
id={`Editor_${editorInstanceRef.current?.paneKey}`}
className="conApp__editorContent"
data-test-subj="request-editor"
onClick={() => {
consoleMenuRef.current?.closePopover();
aceEditorRef.current?.focus();
}}
/>
</div>
</div>
); );
}; };
@ -217,6 +244,3 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
// export default ConsoleInput; // export default ConsoleInput;
export default ConsoleInputUI; export default ConsoleInputUI;

View File

@ -456,7 +456,7 @@ export class IndexPatternsService {
}); });
const indexPattern = await this.create(spec, true); const indexPattern = await this.create(spec, true);
indexPatternCache.set(id, indexPattern); indexPatternCache.set(cacheID, indexPattern);
if (isSaveRequired) { if (isSaveRequired) {
try { try {
this.updateSavedObject(indexPattern); this.updateSavedObject(indexPattern);

View File

@ -1,8 +1,8 @@
@import '../../../../../core/public/variables.scss'; @import "../../../../../core/public/variables.scss";
.kbnDocViewerTable { .kbnDocViewerTable {
margin-top: $euiSizeS; margin-top: $euiSizeS;
font-size: 12px; font-size: 12px;
.kbnDocViewer__buttons{ .kbnDocViewer__buttons {
padding-top: 0px !important; padding-top: 0px !important;
} }
} }
@ -20,6 +20,7 @@
} }
.kbnDocViewer__field { .kbnDocViewer__field {
padding-top: 8px; padding-top: 8px;
max-width: 250px;
} }
.dscFieldName { .dscFieldName {
@ -73,4 +74,3 @@
.kbnDocViewer__warning { .kbnDocViewer__warning {
margin-right: $euiSizeS; margin-right: $euiSizeS;
} }

View File

@ -16,11 +16,11 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, { useState } from 'react'; import React, { useState } from "react";
import { escapeRegExp } from 'lodash'; import { escapeRegExp } from "lodash";
import { DocViewTableRow } from './table_row'; import { DocViewTableRow } from "./table_row";
import { arrayContainsObjects, trimAngularSpan } from './table_helper'; import { arrayContainsObjects, trimAngularSpan } from "./table_helper";
import { DocViewRenderProps } from '../../doc_views/doc_views_types'; import { DocViewRenderProps } from "../../doc_views/doc_views_types";
const COLLAPSE_LINE_LENGTH = 350; const COLLAPSE_LINE_LENGTH = 350;
@ -34,8 +34,10 @@ export function DocViewTable({
}: DocViewRenderProps) { }: DocViewRenderProps) {
const mapping = indexPattern.fields.getByName; const mapping = indexPattern.fields.getByName;
const flattened = indexPattern.flattenHit(hit); const flattened = indexPattern.flattenHit(hit);
const formatted = indexPattern.formatHit(hit, 'html'); const formatted = indexPattern.formatHit(hit, "html");
const [fieldRowOpen, setFieldRowOpen] = useState({} as Record<string, boolean>); const [fieldRowOpen, setFieldRowOpen] = useState(
{} as Record<string, boolean>
);
function toggleValueCollapse(field: string) { function toggleValueCollapse(field: string) {
fieldRowOpen[field] = fieldRowOpen[field] !== true; fieldRowOpen[field] = fieldRowOpen[field] !== true;
@ -64,8 +66,10 @@ export function DocViewTable({
} }
: undefined; : undefined;
const isArrayOfObjects = const isArrayOfObjects =
Array.isArray(flattened[field]) && arrayContainsObjects(flattened[field]); Array.isArray(flattened[field]) &&
const displayUnderscoreWarning = !mapping(field) && field.indexOf('_') === 0; arrayContainsObjects(flattened[field]);
const displayUnderscoreWarning =
!mapping(field) && field.indexOf("_") === 0;
const displayNoMappingWarning = const displayNoMappingWarning =
!mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects; !mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects;
@ -107,10 +111,16 @@ export function DocViewTable({
!indexPattern.fields.getByName(field) && !indexPattern.fields.getByName(field) &&
!!indexPattern.fields.getAll().find((patternField) => { !!indexPattern.fields.getAll().find((patternField) => {
// We only want to match a full path segment // We only want to match a full path segment
const nestedRootRegex = new RegExp(escapeRegExp(field) + '(\\.|$)'); const nestedRootRegex = new RegExp(
return nestedRootRegex.test(patternField.subType?.nested?.path ?? ''); escapeRegExp(field) + "(\\.|$)"
);
return nestedRootRegex.test(
patternField.subType?.nested?.path ?? ""
);
}); });
const fieldType = isNestedField ? 'nested' : indexPattern.fields.getByName(field)?.type; const fieldType = isNestedField
? "nested"
: indexPattern.fields.getByName(field)?.type;
return ( return (
<DocViewTableRow <DocViewTableRow
@ -122,7 +132,9 @@ export function DocViewTable({
displayNoMappingWarning={displayNoMappingWarning} displayNoMappingWarning={displayNoMappingWarning}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
isCollapsible={isCollapsible} isCollapsible={isCollapsible}
isColumnActive={Array.isArray(columns) && columns.includes(field)} isColumnActive={
Array.isArray(columns) && columns.includes(field)
}
onFilter={filter} onFilter={filter}
onToggleCollapse={() => toggleValueCollapse(field)} onToggleCollapse={() => toggleValueCollapse(field)}
onToggleColumn={toggleColumn} onToggleColumn={toggleColumn}

View File

@ -0,0 +1,23 @@
import * as React from "react";
export default function useAsync(callback, dependencies = []) {
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState();
const [value, setValue] = React.useState();
const callbackMemoized = React.useCallback(() => {
setLoading(true);
setError(undefined);
setValue(undefined);
callback()
.then(setValue)
.catch(setError)
.finally(() => setLoading(false));
}, dependencies);
React.useEffect(() => {
callbackMemoized();
}, [callbackMemoized]);
return { loading, error, value };
}

View File

@ -0,0 +1,21 @@
import useAsync from "./use_async";
const DEFAULT_OPTIONS = {
headers: { "Content-Type": "application/json" },
};
export default function useFetch(url, options = {}, dependencies = []) {
return useAsync(() => {
if (options.queryParams) {
url +=
"?" +
Object.entries(options.queryParams)
.map((kvs) => kvs.join("="))
.join("&");
}
return fetch(url, { ...DEFAULT_OPTIONS, ...options }).then((res) => {
if (res.ok) return res.json();
return res.json().then((json) => Promise.reject(json));
});
}, dependencies);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
.vizChartContainer {
.vizChartContainer{
padding: 0px; padding: 0px;
border-bottom: 1px solid rgb(232, 232, 232);
} }
.vizChartItem{ .vizChartItem {
background: white!important; background: white !important;
margin-bottom: 5px; margin-bottom: 5px;
} }
.vizChartTitle{ .vizChartTitle {
color: red; color: red;
position: absolute; position: absolute;
font-size: 14px; font-size: 14px;

View File

@ -0,0 +1,225 @@
import * as React from "react";
import {
Axis,
Chart,
CurveType,
LineSeries,
niceTimeFormatByDay,
Position,
ScaleType,
Settings,
timeFormatter,
} from "@elastic/charts";
import useFetch from "@/lib/hooks/use_fetch";
import { ESPrefix } from "@/services/common";
import styles from "../Metrics.less";
import { Spin, Radio, Select, Skeleton } from "antd";
import { formatter, getFormatter, getNumFormatter } from "../format";
import "./node_metric.scss";
import { calculateBounds } from "@/components/kibana/data/common/query/timefilter";
import moment from "moment";
export default ({ clusterID, timezone, timeRange, handleTimeChange }) => {
const [filter, setFilter] = React.useState({
top: "5",
index_name: undefined,
});
const topChange = (e) => {
setFilter({
index_name: undefined,
top: e.target.value,
});
};
const indexValueChange = (value) => {
setFilter({
top: undefined,
index_name: value,
});
};
const queryParams = React.useMemo(() => {
const bounds = calculateBounds({
from: timeRange.min,
to: timeRange.max,
});
return {
...filter,
min: bounds.min.valueOf(),
max: bounds.max.valueOf(),
};
}, [filter, timeRange]);
const { loading, error, value } = useFetch(
`${ESPrefix}/${clusterID}/index_metrics`,
{
queryParams: queryParams,
},
[clusterID, queryParams]
);
const metrics = React.useMemo(() => {
return Object.values(value?.metrics || {}).sort(
(a, b) => a.order - b.order
);
}, [value]);
const chartRefs = React.useRef();
React.useEffect(() => {
chartRefs.current = metrics.map(() => {
return React.createRef();
});
}, [metrics]);
const { value: indices } = useFetch(
`${ESPrefix}/${clusterID}/_cat/indices`,
{},
[clusterID]
);
const indexNames = React.useMemo(() => {
return Object.keys(indices || {});
}, [indices]);
const pointerUpdate = (event) => {
chartRefs.current.forEach((ref) => {
if (ref.current) {
ref.current.dispatchExternalPointerEvent(event);
}
});
};
const handleChartBrush = ({ x }) => {
if (!x) {
return;
}
const [from, to] = x;
if (typeof handleTimeChange == "function") {
handleTimeChange({
start: moment(from).toISOString(),
end: moment(to).toISOString(),
});
}
};
return (
<div>
<div className="px">
<div className="metric-control">
<div className="selector">
<div className="top_radio">
<Radio.Group onChange={topChange} value={filter.top}>
<Radio.Button value="5">Top5</Radio.Button>
<Radio.Button value="10">Top10</Radio.Button>
<Radio.Button value="15">Top15</Radio.Button>
<Radio.Button value="20">Top20</Radio.Button>
</Radio.Group>
</div>
<div className="value-selector">
<Select
style={{ width: 200 }}
onChange={indexValueChange}
placeholder="请选择索引"
value={filter.index_name}
showSearch={true}
>
{indexNames.map((name) => (
<Select.Option key={name}>{name}</Select.Option>
))}
</Select>
</div>
</div>
</div>
</div>
<div className="px">
<Skeleton active loading={loading} paragraph={{ rows: 20 }}>
{Object.keys(metrics).map((e, i) => {
let axis = metrics[e].axis;
let lines = metrics[e].lines;
let disableHeaderFormat = false;
let headerUnit = "";
return (
<div key={e} className={styles.vizChartContainer}>
<Chart
size={[, 200]}
className={styles.vizChartItem}
ref={chartRefs.current[i]}
>
<Settings
// theme={theme}
pointerUpdateDebounce={0}
pointerUpdateTrigger="x"
externalPointerEvents={{
tooltip: { visible: true },
}}
onPointerUpdate={pointerUpdate}
showLegend
legendPosition={Position.Top}
onBrushEnd={handleChartBrush}
tooltip={{
headerFormatter: disableHeaderFormat
? undefined
: ({ value }) =>
`${formatter.full_dates(value)}${
headerUnit ? ` ${headerUnit}` : ""
}`,
}}
debug={false}
/>
<Axis
id="{e}-bottom"
position={Position.Bottom}
showOverlappingTicks
labelFormat={timeRange.timeFormatter}
tickFormat={timeRange.timeFormatter}
/>
{axis.map((item) => {
return (
<Axis
key={e + "-" + item.id}
id={e + "-" + item.id}
showGridLines={item.showGridLines}
groupId={item.group}
title={item.title}
position={item.position}
ticks={item.ticks}
labelFormat={getFormatter(
item.formatType,
item.labelFormat
)}
tickFormat={getFormatter(
item.formatType,
item.tickFormat
)}
/>
);
})}
{lines.map((item) => {
return (
<LineSeries
key={item.metric.label}
id={item.metric.label}
groupId={item.metric.group}
timeZone={timezone}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
tickFormat={getFormatter(
item.metric.formatType,
item.metric.tickFormat,
item.metric.units
)}
yAccessors={[1]}
data={item.data}
curve={CurveType.CURVE_MONOTONE_X}
/>
);
})}
</Chart>
</div>
);
})}
</Skeleton>
</div>
</div>
);
};

View File

@ -0,0 +1,223 @@
import * as React from "react";
import {
Axis,
Chart,
CurveType,
LineSeries,
niceTimeFormatByDay,
Position,
ScaleType,
Settings,
timeFormatter,
} from "@elastic/charts";
import useFetch from "@/lib/hooks/use_fetch";
import { ESPrefix } from "@/services/common";
import styles from "../Metrics.less";
import { Spin, Radio, Select, Skeleton } from "antd";
import { formatter, getFormatter, getNumFormatter } from "../format";
import "./node_metric.scss";
import { calculateBounds } from "@/components/kibana/data/common/query/timefilter";
import moment from "moment";
export default ({ clusterID, timezone, timeRange, handleTimeChange }) => {
const [filter, setFilter] = React.useState({
top: "5",
node_name: undefined,
});
const topChange = (e) => {
setFilter({
node_name: undefined,
top: e.target.value,
});
};
const nodeValueChange = (value) => {
setFilter({
top: undefined,
node_name: value,
});
};
const queryParams = React.useMemo(() => {
const bounds = calculateBounds({
from: timeRange.min,
to: timeRange.max,
});
return {
...filter,
min: bounds.min.valueOf(),
max: bounds.max.valueOf(),
};
}, [filter, timeRange]);
const { loading, error, value } = useFetch(
`${ESPrefix}/${clusterID}/node_metrics`,
{
queryParams: queryParams,
},
[clusterID, queryParams]
);
const metrics = React.useMemo(() => {
return Object.values(value?.metrics || {}).sort(
(a, b) => a.order - b.order
);
}, [value]);
const chartRefs = React.useRef();
React.useEffect(() => {
chartRefs.current = metrics.map(() => {
return React.createRef();
});
}, [metrics]);
const { value: nodes } = useFetch(`${ESPrefix}/${clusterID}/nodes`, {}, [
clusterID,
]);
const nodeNames = React.useMemo(() => {
return Object.keys(nodes || {}).map((k) => nodes[k].name);
}, [nodes]);
const pointerUpdate = (event) => {
chartRefs.current.forEach((ref) => {
if (ref.current) {
ref.current.dispatchExternalPointerEvent(event);
}
});
};
const handleChartBrush = ({ x }) => {
if (!x) {
return;
}
const [from, to] = x;
if (typeof handleTimeChange == "function") {
handleTimeChange({
start: moment(from).toISOString(),
end: moment(to).toISOString(),
});
}
};
return (
<div>
<div className="px">
<div className="metric-control">
<div className="selector">
<div className="top_radio">
<Radio.Group onChange={topChange} value={filter.top}>
<Radio.Button value="5">Top5</Radio.Button>
<Radio.Button value="10">Top10</Radio.Button>
<Radio.Button value="15">Top15</Radio.Button>
<Radio.Button value="20">Top20</Radio.Button>
</Radio.Group>
</div>
<div className="value-selector">
<Select
style={{ width: 200 }}
onChange={nodeValueChange}
placeholder="请选择节点"
value={filter.node_name}
showSearch={true}
>
{nodeNames.map((name) => (
<Select.Option key={name}>{name}</Select.Option>
))}
</Select>
</div>
</div>
</div>
</div>
<div className="px">
<Skeleton active loading={loading} paragraph={{ rows: 20 }}>
{Object.keys(metrics).map((e, i) => {
let axis = metrics[e].axis;
let lines = metrics[e].lines;
let disableHeaderFormat = false;
let headerUnit = "";
return (
<div key={e} className={styles.vizChartContainer}>
<Chart
size={[, 200]}
className={styles.vizChartItem}
ref={chartRefs.current[i]}
>
<Settings
// theme={theme}
pointerUpdateDebounce={0}
pointerUpdateTrigger="x"
externalPointerEvents={{
tooltip: { visible: true },
}}
onPointerUpdate={pointerUpdate}
showLegend
legendPosition={Position.Top}
onBrushEnd={handleChartBrush}
tooltip={{
headerFormatter: disableHeaderFormat
? undefined
: ({ value }) =>
`${formatter.full_dates(value)}${
headerUnit ? ` ${headerUnit}` : ""
}`,
}}
debug={false}
/>
<Axis
id="{e}-bottom"
position={Position.Bottom}
showOverlappingTicks
labelFormat={timeRange.timeFormatter}
tickFormat={timeRange.timeFormatter}
/>
{axis.map((item) => {
return (
<Axis
key={e + "-" + item.id}
id={e + "-" + item.id}
showGridLines={item.showGridLines}
groupId={item.group}
title={item.title}
position={item.position}
ticks={item.ticks}
labelFormat={getFormatter(
item.formatType,
item.labelFormat
)}
tickFormat={getFormatter(
item.formatType,
item.tickFormat
)}
/>
);
})}
{lines.map((item) => {
return (
<LineSeries
key={item.metric.label}
id={item.metric.label}
groupId={item.metric.group}
timeZone={timezone}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
tickFormat={getFormatter(
item.metric.formatType,
item.metric.tickFormat,
item.metric.units
)}
yAccessors={[1]}
data={item.data}
curve={CurveType.CURVE_MONOTONE_X}
/>
);
})}
</Chart>
</div>
);
})}
</Skeleton>
</div>
</div>
);
};

View File

@ -0,0 +1,13 @@
.metric-control {
display: flex;
.selector {
margin-left: auto;
display: flex;
.value-selector {
margin-left: 5px;
}
}
}
.px {
padding: 0 15px;
}

View File

@ -0,0 +1,46 @@
import numeral from "numeral";
import { timeFormatter, niceTimeFormatByDay } from "@elastic/charts";
import { DateTime } from "luxon";
const unitArr = Array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB");
export const formatter = {
bytes: (value) => {
if (isNaN(value) || null == value || value === "" || value == 0) {
return "0B";
}
var index = 0;
var srcsize = parseFloat(value);
index = Math.floor(Math.log(srcsize) / Math.log(1024));
var size = srcsize / Math.pow(1024, index);
size = size.toFixed(1);
return size + unitArr[index];
},
dates: (day) => timeFormatter(niceTimeFormatByDay(day)),
full_dates: (d) => DateTime.fromMillis(d).toFormat("yyyy-MM-dd HH:mm:ss"),
utc_full_dates: (d) =>
DateTime.fromMillis(d)
.toUTC()
.toFormat("yyyy-MM-dd HH:mm:ss"),
ratio: (d) => `${Number(d).toFixed(0)}%`,
highPrecisionNumber: (d) => numeral(d).format("0.0000"),
lowPrecisionNumber: (d) => numeral(d).format("0.0"),
number: (d) => numeral(d).format("0,0"),
};
export function getFormatter(type, format, units) {
switch (type) {
case "bytes":
return formatter.bytes;
case "ratio":
return formatter.ratio;
case "num":
return getNumFormatter(format, units);
default:
return formatter.lowPrecisionNumber;
}
}
export function getNumFormatter(format, units) {
return (d) => numeral(d).format(format) + (units ? ` ${units}` : "");
}

View File

@ -1,50 +1,56 @@
import {getClusterOverview, getClusterNodeStats, getClusterList} from "@/services/dashboard"; import {
import {getClusterMetrics} from "@/services/cluster"; getClusterOverview,
getClusterNodeStats,
getClusterList,
} from "@/services/dashboard";
import { getClusterMetrics } from "@/services/cluster";
export default { export default {
namespace: 'clusterMonitor', namespace: "clusterMonitor",
state: { state: {},
effects: {
*fetchClusterOverview({ payload, callback }, { call, put }) {
let clusterData = yield call(getClusterOverview, payload);
yield put({ type: "saveData", payload: clusterData });
if (callback && typeof callback == "function") {
callback(clusterData);
}
}, },
effects:{ *fetchClusterMetrics({ payload, callback }, { call, put }) {
*fetchClusterOverview({payload, callback}, {call, put}){ let clusterMetrics = yield call(getClusterMetrics, payload);
let clusterData = yield call(getClusterOverview, payload); yield put({ type: "saveData", payload: clusterMetrics });
yield put({type: 'saveData', payload: clusterData}) if (callback && typeof callback == "function") {
if(callback && typeof callback == 'function'){ callback(clusterMetrics);
callback(clusterData); console.log("finished call:" + params.cluster_id);
} }
}, return clusterMetrics;
*fetchClusterMetrics({payload, callback}, {call, put}){
let clusterMetrics = yield call(getClusterMetrics, payload);
yield put({type: 'saveData', payload: clusterMetrics})
if(callback && typeof callback == 'function'){
callback(clusterMetrics);
console.log("finished call:"+params.cluster_id);
}
},
*fetchClusterNodeStats({callback}, {call, put}){
let nodesStats = yield call(getClusterNodeStats);
//yield put({type: 'saveData', payload: nodesStats})
if(callback && typeof callback == 'function'){
callback(nodesStats);
}
},
*fetchClusterList({callback}, {call, put}){
let clusterData = yield call(getClusterList);
yield put({type: 'saveData', payload: {
clusterList: clusterData
}})
if(callback && typeof callback == 'function'){
callback(clusterData);
}
}
}, },
reducers:{ *fetchClusterNodeStats({ callback }, { call, put }) {
saveData(state, {payload}){ let nodesStats = yield call(getClusterNodeStats);
return { //yield put({type: 'saveData', payload: nodesStats})
...state, if (callback && typeof callback == "function") {
...payload callback(nodesStats);
}; }
},
*fetchClusterList({ callback }, { call, put }) {
let clusterData = yield call(getClusterList);
yield put({
type: "saveData",
payload: {
clusterList: clusterData,
}, },
} });
if (callback && typeof callback == "function") {
callback(clusterData);
}
},
},
reducers: {
saveData(state, { payload }) {
return {
...state,
...payload,
};
},
},
}; };

View File

@ -392,6 +392,9 @@ const Discover = (props) => {
}, },
[props.selectedCluster, updateQuery] [props.selectedCluster, updateQuery]
); );
const document = useMemo(() => {
saveDocument, deleteDocument;
}, [saveDocument, deleteDocument]);
const [settingsVisible, setSettingsVisible] = React.useState(false); const [settingsVisible, setSettingsVisible] = React.useState(false);
@ -627,7 +630,7 @@ const Discover = (props) => {
onMoveColumn={onMoveColumn} onMoveColumn={onMoveColumn}
onAddColumn={onAddColumn} onAddColumn={onAddColumn}
onChangeSortOrder={onSort} onChangeSortOrder={onSort}
document={{ saveDocument, deleteDocument }} document={document}
hits={rows} hits={rows}
/> />
</div> </div>

View File

@ -10,7 +10,6 @@ import {
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useLayoutEffect,
} from "react"; } from "react";
import { useLocalStorage } from "@/lib/hooks/storage"; import { useLocalStorage } from "@/lib/hooks/storage";
import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings"; import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings";
@ -195,12 +194,16 @@ export const ConsoleUI = ({
const [tabState, dispatch] = useReducer(consoleTabReducer, localState); const [tabState, dispatch] = useReducer(consoleTabReducer, localState);
useEffect(() => { useEffect(() => {
if (tabState.panes.length == 0) { const panes = tabState.panes.filter((pane: any) => {
return typeof clusterMap[pane.cluster_id] != "undefined";
});
if (panes.length == 0) {
removeLocalState(); removeLocalState();
return; return;
} }
setLocalState(tabState); setLocalState(tabState);
}, [tabState]); }, [tabState, clusterMap]);
const saveEditorContent = useCallback( const saveEditorContent = useCallback(
(content) => { (content) => {

View File

@ -1,21 +1,21 @@
import { Button, Dropdown, List, Spin, message, Icon, Input } from 'antd'; import { Button, Dropdown, List, Spin, message, Icon, Input } from "antd";
import * as React from 'react'; import * as React from "react";
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from "react-infinite-scroller";
import styles from '@/components/GlobalHeader/DropdownSelect.less'; import styles from "@/components/GlobalHeader/DropdownSelect.less";
import _ from "lodash"; import _ from "lodash";
import {DropdownItem} from '@/components/GlobalHeader/DropdownItem'; import { DropdownItem } from "@/components/GlobalHeader/DropdownItem";
import {HealthStatusCircle} from '@/components/infini/health_status_circle' import { HealthStatusCircle } from "@/components/infini/health_status_circle";
class NewTabMenu extends React.Component{ class NewTabMenu extends React.Component {
handleItemClick = (item)=>{ handleItemClick = (item) => {
const onItemClick = this.props.onItemClick; const onItemClick = this.props.onItemClick;
if(onItemClick && typeof onItemClick == 'function'){ if (onItemClick && typeof onItemClick == "function") {
onItemClick(item) onItemClick(item);
} }
} };
constructor(props){ constructor(props) {
super(props); super(props);
this.state={ this.state = {
loading: false, loading: false,
hasMore: (props.data || []).length > props.size, hasMore: (props.data || []).length > props.size,
data: (props.data || []).slice(0, props.size || 10), data: (props.data || []).slice(0, props.size || 10),
@ -24,34 +24,41 @@ class NewTabMenu extends React.Component{
dataSourceKey: 1, dataSourceKey: 1,
selectedIndex: -1, selectedIndex: -1,
overlayVisible: false, overlayVisible: false,
};
}
componentDidMount() {}
componentDidUpdate(preProps, preState) {
if (preProps.data.length != preState.dataSource.length) {
const newData = [...preProps.data];
this.setState({
dataSource: newData,
data: newData,
hasMore: newData.length > this.props.size,
selectedIndex: -1,
});
} }
} }
componentDidMount(){
}
handleInfiniteOnLoad = (current) => { handleInfiniteOnLoad = (current) => {
let {size } = this.props; let { size } = this.props;
let targetLength = current * size; let targetLength = current * size;
let {hasMore, dataSource} = this.state; let { hasMore, dataSource } = this.state;
if(dataSource.length < targetLength){ if (dataSource.length < targetLength) {
targetLength = dataSource.length; targetLength = dataSource.length;
hasMore = false hasMore = false;
} }
const newData = this.state.dataSource.slice(0, targetLength); const newData = this.state.dataSource.slice(0, targetLength);
this.setState({ this.setState({
data: newData, data: newData,
hasMore: hasMore, hasMore: hasMore,
}) });
};
}
handleInputChange = (e) =>{ handleInputChange = (e) => {
const name = e.target.value; const name = e.target.value;
const newData = this.props.data.filter(item=>{ const newData = this.props.data.filter((item) => {
return item.name.includes(name); return item.name.includes(name);
}); });
this.setState({ this.setState({
@ -60,97 +67,129 @@ class NewTabMenu extends React.Component{
data: newData, data: newData,
hasMore: newData.length > this.props.size, hasMore: newData.length > this.props.size,
selectedIndex: -1, selectedIndex: -1,
}) });
};
}
selectOffset = (offset)=> { selectOffset = (offset) => {
let {selectedIndex, data} = this.state; let { selectedIndex, data } = this.state;
const len = data.length; const len = data.length;
selectedIndex = (selectedIndex + offset + len) % len; selectedIndex = (selectedIndex + offset + len) % len;
// const item = data[selectedIndex]; // const item = data[selectedIndex];
this.setState({ this.setState({
selectedIndex, selectedIndex,
}) });
} };
onKeyDown = (e) => { onKeyDown = (e) => {
const { which } = e; const { which } = e;
// e.preventDefault(); // e.preventDefault();
switch (which) { switch (which) {
case 38: case 38:
this.selectOffset(-1); this.selectOffset(-1);
e.preventDefault(); e.preventDefault();
e.stopPropagation() e.stopPropagation();
break; break;
case 40: case 40:
this.selectOffset(1); this.selectOffset(1);
e.stopPropagation() e.stopPropagation();
break; break;
case 13: case 13:
const {data, selectedIndex} = this.state; const { data, selectedIndex } = this.state;
if(selectedIndex > -1){ if (selectedIndex > -1) {
this.handleItemClick(data[selectedIndex]); this.handleItemClick(data[selectedIndex]);
this.setState({ overlayVisible: false }) this.setState({ overlayVisible: false });
} }
break; break;
} }
} };
render() {
render(){ const { clusterStatus } = this.props;
const {clusterStatus} = this.props; const menu = (
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}> <div className={styles.dropmenu} style={{ width: this.props.width }}>
<div className={styles.infiniteContainer} style={{height: this.props.height}} <div
onMouseEnter={()=>{this.searchInputRef.focus()}} className={styles.infiniteContainer}
tabIndex="0" onKeyDown={this.onKeyDown}> style={{ height: this.props.height }}
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}> onMouseEnter={() => {
<input className={styles['btn-ds']} style={{outline:'none'}} this.searchInputRef.focus();
ref={(ref)=>{this.searchInputRef= ref;}} }}
onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} /> tabIndex="0"
</div> onKeyDown={this.onKeyDown}
<InfiniteScroll
initialLoad={this.state.initialLoad}
loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore}
useWindow={false}
> >
<div className={styles.dslist}> <div
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>} className={styles.filter}
{(this.state.data || []).map((item, idx)=>{ style={{ paddingTop: 10, paddingBottom: 0 }}
const cstatus = clusterStatus ? clusterStatus[item.id] : null; >
return <DropdownItem key={item.id} <input
clusterItem={item} className={styles["btn-ds"]}
clusterStatus={cstatus} style={{ outline: "none" }}
isSelected={this.state.selectedIndex == idx} ref={(ref) => {
onClick={() => { this.searchInputRef = ref;
this.handleItemClick(item)
}} }}
/> onChange={this.handleInputChange}
})} placeholder="输入集群名称查找"
value={this.state.displayValue || ""}
/>
</div>
<InfiniteScroll
initialLoad={this.state.initialLoad}
loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore}
useWindow={false}
>
<div className={styles.dslist}>
{(!this.state.data || !this.state.data.length) && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
匹配不到集群(匹配规则为前缀匹配)
</div>
)}
{(this.state.data || []).map((item, idx) => {
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return (
<DropdownItem
key={item.id}
clusterItem={item}
clusterStatus={cstatus}
isSelected={this.state.selectedIndex == idx}
onClick={() => {
this.handleItemClick(item);
}}
/>
);
})}
</div>
</InfiniteScroll>
</div> </div>
</InfiniteScroll> {!this.state.loading && this.state.hasMore && (
<div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
pull load more
</div>
)}
</div> </div>
{!this.state.loading && this.state.hasMore && ( );
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}>
pull load more
</div>
)}
</div>);
return ( return (
<div> <div>
<Dropdown overlay={menu} placement="bottomLeft" <Dropdown
visible={this.state.overlayVisible} overlay={menu}
onVisibleChange={(flag)=>{ placement="bottomLeft"
this.setState({ overlayVisible: flag }); visible={this.state.overlayVisible}
}}> onVisibleChange={(flag) => {
{this.props.children} this.setState({ overlayVisible: flag });
</Dropdown> }}
</div> >
) {this.props.children}
</Dropdown>
</div>
);
} }
} }
export default NewTabMenu; export default NewTabMenu;

View File

@ -1,166 +1,171 @@
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig, import {
tryConnect} from "@/services/cluster"; createClusterConfig,
import {message} from "antd"; searchClusterConfig,
import {formatESSearchResult} from '@/lib/elasticsearch/util'; updateClusterConfig,
deleteClusterConfig,
tryConnect,
} from "@/services/cluster";
import { message } from "antd";
import { formatESSearchResult } from "@/lib/elasticsearch/util";
export default { export default {
namespace: 'clusterConfig', namespace: "clusterConfig",
state: { state: {
editMode: '', editMode: "",
editValue: {}, editValue: {},
}, },
effects:{ effects: {
*fetchClusterList({payload}, {call, put, select}){ *fetchClusterList({ payload }, { call, put, select }) {
let res = yield call(searchClusterConfig, payload); let res = yield call(searchClusterConfig, payload);
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return false; return false;
} }
res = formatESSearchResult(res) res = formatESSearchResult(res);
const {clusterStatus} = yield select(state => state.clusterConfig); const { clusterStatus } = yield select((state) => state.clusterConfig);
// for(let item of res.data){ // for(let item of res.data){
// item.status= clusterStatus[item.id] // item.status= clusterStatus[item.id]
// } // }
yield put({ yield put({
type: 'saveData', type: "saveData",
payload: res payload: res,
}) });
}, },
*addCluster({payload}, {call, put, select}) { *addCluster({ payload }, { call, put, select }) {
let res = yield call(createClusterConfig, payload) let res = yield call(createClusterConfig, payload);
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return false; return false;
} }
let {data, total} = yield select(state => state.clusterConfig); let { data, total } = yield select((state) => state.clusterConfig);
if(!data){ if (!data) {
return return;
} }
data.unshift({ data.unshift({
...res._source, ...res._source,
id: res._id, id: res._id,
}); });
yield put({ yield put({
type: 'saveData', type: "saveData",
payload: { payload: {
data, data,
total: { total: {
...total, ...total,
value: total.value + 1 value: total.value + 1,
}, },
} },
}) });
yield put({ yield put({
type: 'global/addCluster', type: "global/addCluster",
payload: { payload: {
...res._source,
id: res._id, id: res._id,
name: res._source.name, },
} });
})
return res; return res;
}, },
*updateCluster({payload}, {call, put, select}) { *updateCluster({ payload }, { call, put, select }) {
let res = yield call(updateClusterConfig, payload) let res = yield call(updateClusterConfig, payload);
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return false; return false;
} }
let {data} = yield select(state => state.clusterConfig); let { data } = yield select((state) => state.clusterConfig);
let idx = data.findIndex((item)=>{ let idx = data.findIndex((item) => {
return item.id === res._id; return item.id === res._id;
}); });
let originalEnabled = data[idx].enabled; let originalEnabled = data[idx].enabled;
data[idx] = { data[idx] = {
...data[idx], ...data[idx],
...res._source ...res._source,
}; };
yield put({ yield put({
type: 'saveData', type: "saveData",
payload: { payload: {
data data,
} },
}) });
//handle global cluster logic //handle global cluster logic
if(originalEnabled !== res._source.enabled){ if (originalEnabled !== res._source.enabled) {
if(res._source.enabled === true) { if (res._source.enabled === true) {
yield put({ yield put({
type: 'global/addCluster', type: "global/addCluster",
payload: { payload: {
id: res._id, id: res._id,
name: res._source.name, name: res._source.name,
} },
}) });
}else{ } else {
yield put({ yield put({
type: 'global/removeCluster', type: "global/removeCluster",
payload: { payload: {
id: res._id, id: res._id,
} },
}) });
} }
}else{ } else {
yield put({ yield put({
type: 'global/updateCluster', type: "global/updateCluster",
payload: { payload: {
id: res._id, id: res._id,
name: res._source.name, name: res._source.name,
} },
}) });
} }
return res; return res;
}, },
*deleteCluster({payload}, {call, put, select}) { *deleteCluster({ payload }, { call, put, select }) {
let res = yield call(deleteClusterConfig, payload) let res = yield call(deleteClusterConfig, payload);
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return false; return false;
} }
let {data, total} = yield select(state => state.clusterConfig); let { data, total } = yield select((state) => state.clusterConfig);
data = data.filter((item)=>{ data = data.filter((item) => {
return item.id !== payload.id; return item.id !== payload.id;
}) });
yield put({ yield put({
type: 'saveData', type: "saveData",
payload: { payload: {
data, data,
total: { total: {
...total, ...total,
value: total.value + 1 value: total.value + 1,
} },
} },
}) });
yield put({ yield put({
type: 'global/removeCluster', type: "global/removeCluster",
payload: { payload: {
id: payload.id id: payload.id,
} },
}) });
return res; return res;
}, },
*doTryConnect({payload}, {call, put, select}) { *doTryConnect({ payload }, { call, put, select }) {
let res = yield call(tryConnect, payload) let res = yield call(tryConnect, payload);
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return false; return false;
} }
yield put({ yield put({
type: 'saveData', type: "saveData",
payload: { payload: {
tempClusterInfo: res, tempClusterInfo: res,
} },
}) });
return res; return res;
} },
}, },
reducers:{ reducers: {
saveData(state, {payload}){ saveData(state, { payload }) {
return { return {
...state, ...state,
...payload, ...payload,
} };
} },
} },
} };

View File

@ -1,6 +1,6 @@
import React, { PureComponent, Fragment } from 'react'; import React, { PureComponent, Fragment } from "react";
import { connect } from 'dva'; import { connect } from "dva";
import {Link} from 'umi'; import { Link } from "umi";
import { import {
Row, Row,
Col, Col,
@ -17,105 +17,31 @@ import {
Menu, Menu,
Table, Table,
Dropdown, Dropdown,
Icon, Popconfirm, Icon,
Popconfirm,
Switch, Switch,
} from 'antd'; } from "antd";
import Editor from '@monaco-editor/react'; // import { loader } from "@monaco-editor/react";
import Editor from "@monaco-editor/react";
import { EuiCodeBlock } from "@elastic/eui";
// loader.config({
// paths: {
// vs: "monaco-editor/min/vs",
// },
// });
import styles from '../../List/TableList.less'; import styles from "../../List/TableList.less";
import {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings'; import { transformSettingsForApi } from "@/lib/elasticsearch/edit_settings";
import PageHeaderWrapper from '@/components/PageHeaderWrapper'; import PageHeaderWrapper from "@/components/PageHeaderWrapper";
import { TagGenerator } from "@/components/kibana/console/components/CommonCommandModal";
const FormItem = Form.Item; const FormItem = Form.Item;
const { TextArea } = Input; const { TextArea } = Input;
const {TabPane} = Tabs; 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 */ /* eslint react/no-multi-comp:0 */
@connect(({ command }) => ({ @connect(({ command }) => ({
command command,
})) }))
@Form.create() @Form.create()
class Index extends PureComponent { class Index extends PureComponent {
@ -125,35 +51,42 @@ class Index extends PureComponent {
expandForm: false, expandForm: false,
formValues: {}, formValues: {},
drawerVisible: false, drawerVisible: false,
editingCommand:{}, editingCommand: {},
indexActiveKey: '1', indexActiveKey: "1",
showSystemIndices: false, showSystemIndices: false,
}; };
columns = [ columns = [
{ {
title: '名称', title: "名称",
dataIndex: 'title', dataIndex: "title",
render: (text, record) => ( render: (text, record) => (
<a onClick={()=>{ <a
this.setState({ onClick={() => {
editingCommand: record, this.setState({
drawerVisible: true, editingCommand: record,
}); drawerVisible: true,
}}>{text}</a> });
) }}
>
{text}
</a>
),
}, },
{ {
title: '标签', title: "标签",
dataIndex: 'tag', dataIndex: "tag",
// render: (val)=>{ render: (val) => {
// return val || 0; return (val || []).join(",");
// } },
}, },
{ {
title: '操作', title: "操作",
render: (text, record) => ( render: (text, record) => (
<Fragment> <Fragment>
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.id)}> <Popconfirm
title="Sure to delete?"
onConfirm={() => this.handleDeleteClick(record.id)}
>
<a>删除</a> <a>删除</a>
</Popconfirm> </Popconfirm>
</Fragment> </Fragment>
@ -162,18 +95,18 @@ class Index extends PureComponent {
]; ];
componentDidMount() { componentDidMount() {
this.fetchData() this.fetchData();
} }
fetchData = (params={})=>{ fetchData = (params = {}) => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: 'command/fetchCommandList', type: "command/fetchCommandList",
payload: { payload: {
...params ...params,
} },
}); });
} };
handleFormReset = () => { handleFormReset = () => {
const { form, dispatch } = this.props; const { form, dispatch } = this.props;
@ -186,65 +119,109 @@ class Index extends PureComponent {
handleDeleteClick = (id) => { handleDeleteClick = (id) => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: 'command/removeCommand', type: "command/removeCommand",
payload: { payload: {
id: id, id: id,
},
}).then((res) => {
if (!res.error) {
message.success("删除成功!");
} }
}); });
}; };
handleSearch = e => { handleSearch = (e) => {
e.preventDefault(); e.preventDefault();
const { dispatch, form } = this.props; const { dispatch, form } = this.props;
form.validateFields((err, fieldsValue) => { form.validateFields((err, fieldsValue) => {
if (err) return; if (err) return;
this.fetchData({ this.fetchData({
title: fieldsValue.name, keyword: fieldsValue.keyword,
from: 0, from: 0,
size: 10, size: 10,
}) });
this.setState({ this.setState({
searchKey: fieldsValue.name, searchKey: fieldsValue.keyword,
}); });
}); });
}; };
handleModalVisible = flag => { handleModalVisible = (flag) => {
this.setState({ this.setState({
modalVisible: !!flag, modalVisible: !!flag,
}); });
}; };
handleIndexTabChanged = (activeKey, indexName) => { handleIndexTabChanged = (activeKey, indexName) => {};
} handleEditorDidMount = (editorName, editor) => {
handleEditorDidMount = (editorName, editor)=>{
this[editorName] = editor; this[editorName] = editor;
} };
handleIndexSettingsSaveClick = (indexName)=>{ buildRawCommonCommandRequest(cmd) {
} const { requests } = cmd;
buildRawCommonCommandRequest(cmd){ if (!requests) {
const {requests} = cmd; return "";
if(!requests){
return '';
} }
const strReqs = requests.map((req)=>{ const strReqs = requests.map((req) => {
const {method, path, body} = req; const { method, path, body } = req;
return `${method} ${path}\n${body}`; return `${method} ${path}\n${body}`;
}) });
return strReqs.join('\n'); return strReqs.join("\n");
} }
handleRereshClick=()=>{ handleRereshClick = () => {
const {searchKey} = this.state; const { searchKey } = this.state;
this.fetchData({ this.fetchData({
title: searchKey, title: searchKey,
}) });
} };
handleSaveClick = () => {
// console.log(this.state.editingCommand);
const { dispatch } = this.props;
dispatch({
type: "command/updateCommand",
payload: {
...this.state.editingCommand,
},
}).then((res) => {
if (!res.error) {
this.setState({
drawerVisible: false,
editingCommand: {},
});
message.success("保存成功!");
this.fetchData();
}
});
};
onEditTitleChange = (e) => {
this.setState({
editingCommand: {
...this.state.editingCommand,
title: e.target.value,
},
});
};
onEditTagChange = (val) => {
this.setState({
editingCommand: {
...this.state.editingCommand,
tag: val,
},
});
};
render() { render() {
const {data, total} = this.props.command; const { data, total } = this.props.command;
const { modalVisible, updateModalVisible, updateFormValues, drawerVisible, editingCommand } = this.state; const {
modalVisible,
updateModalVisible,
updateFormValues,
drawerVisible,
editingCommand,
} = this.state;
const parentMethods = { const parentMethods = {
handleAdd: this.handleAdd, handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible, handleModalVisible: this.handleModalVisible,
@ -253,8 +230,10 @@ class Index extends PureComponent {
handleUpdateModalVisible: this.handleUpdateModalVisible, handleUpdateModalVisible: this.handleUpdateModalVisible,
handleUpdate: this.handleUpdate, handleUpdate: this.handleUpdate,
}; };
const {form: { getFieldDecorator }} = this.props; const {
form: { getFieldDecorator },
} = this.props;
return ( return (
<PageHeaderWrapper> <PageHeaderWrapper>
<Card bordered={false}> <Card bordered={false}>
@ -263,8 +242,10 @@ class Index extends PureComponent {
<Form onSubmit={this.handleSearch} layout="inline"> <Form onSubmit={this.handleSearch} layout="inline">
<Row gutter={{ md: 8, lg: 24, xl: 48 }}> <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}> <Col md={8} sm={24}>
<FormItem label="名称"> <FormItem label="关键词">
{getFieldDecorator('name')(<Input placeholder="请输入" />)} {getFieldDecorator("keyword")(
<Input placeholder="请输入" />
)}
</FormItem> </FormItem>
</Col> </Col>
<Col md={8} sm={24}> <Col md={8} sm={24}>
@ -272,55 +253,99 @@ class Index extends PureComponent {
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
查询 查询
</Button> </Button>
<Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}> <Button
style={{ marginLeft: 8 }}
onClick={this.handleFormReset}
>
重置 重置
</Button> </Button>
</span> </span>
</Col> </Col>
<Col md={8} sm={24} style={{textAlign:"right"}}> {/* <Col md={8} sm={24} style={{ textAlign: "right" }}>
<Button icon="redo" style={{marginRight:10}} onClick={this.handleRereshClick}>刷新</Button> <Button
</Col> icon="redo"
style={{ marginRight: 10 }}
onClick={this.handleRereshClick}
>
刷新
</Button>
</Col> */}
</Row> </Row>
</Form> </Form>
</div> </div>
<Table bordered <Table
bordered
dataSource={data} dataSource={data}
rowKey='id' rowKey="id"
pagination={ pagination={{ pageSize: 10 }}
{pageSize: 10,}
}
columns={this.columns} columns={this.columns}
/> />
</div> </div>
</Card> </Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} /> <Drawer
<Drawer title={editingCommand.title} // title={editingCommand.title}
title="常用命令"
visible={drawerVisible} visible={drawerVisible}
onClose={()=>{ onClose={() => {
this.setState({ this.setState({
drawerVisible: false, drawerVisible: false,
indexActiveKey: '1', indexActiveKey: "1",
}); });
}} }}
width={720} width={720}
> >
<div style={{border: '1px solid rgb(232, 232, 232)'}}> <div
<Editor style={{ display: "flex", alignItems: "center", marginBottom: 15 }}
height="300px" >
language="text" <div>标题</div>
theme="light" <div>
value={this.buildRawCommonCommandRequest(editingCommand)} <Input
options={{ value={editingCommand.title}
readOnly: true, onChange={this.onEditTitleChange}
minimap: { style={{ width: 250 }}
enabled: false, />
}, </div>
tabSize: 2, </div>
wordBasedSuggestions: true, <div
}} style={{ display: "flex", alignItems: "center", marginBottom: 15 }}
onMount={(editor)=>this.handleEditorDidMount('indexSettingsEditor', editor)} >
/> <div>标签</div>
<div>
<TagGenerator
value={editingCommand.tag}
onChange={this.onEditTagChange}
/>
{/* <Input style={{ width: 250 }} /> */}
</div>
</div>
<div>内容</div>
<div style={{ border: "1px solid rgb(232, 232, 232)" }}>
<EuiCodeBlock language="json" style={{ height: 300 }} isCopyable>
{this.buildRawCommonCommandRequest(editingCommand)}
</EuiCodeBlock>
{/* <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("commandEditor", editor)
}
/> */}
</div>
<div style={{ marginTop: 15, textAlign: "right" }}>
<Button type="primary" onClick={this.handleSaveClick}>
保存
</Button>
</div> </div>
</Drawer> </Drawer>
</PageHeaderWrapper> </PageHeaderWrapper>

View File

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

View File

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