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,28 +1,28 @@
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' },
// { // {
@ -34,33 +34,29 @@ export default [
// ], // ],
// }, // },
{ {
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', path: "/cluster/monitoring/:cluster_id",
name: 'cluster', name: "cluster",
component: './Cluster/ClusterMonitor', component: "./Cluster/ClusterMonitor",
hideInMenu: true, hideInMenu: true,
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, },
], {
}, { path: "/cluster/metrics/",
path: '/cluster/metrics/', name: "monitoring",
name: 'monitoring', component: "./Cluster/Metrics",
component: './Cluster/Metrics', routes: [{ path: "/", redirect: "/" }],
routes:[ },
{ path: '/', redirect: '/' }, {
], path: "/cluster/metrics/:cluster_id",
}, { name: "monitoring",
path: '/cluster/metrics/:cluster_id', component: "./Cluster/Metrics",
name: 'monitoring',
component: './Cluster/Metrics',
hideInMenu: true, hideInMenu: true,
}, },
// { // {
@ -91,7 +87,7 @@ export default [
// } // }
// ] // ]
// }, // },
] ],
}, },
//devtools //devtools
// { // {
@ -106,38 +102,36 @@ export default [
//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",
component: "./Alerting/pages/Overview/Overview",
name: "overview",
},
{
routes: [{ path: "/", redirect: "/" }],
path: "/alerting/monitor",
component: "./Alerting/index",
name: "monitor",
},
{
routes: [{ path: "/", redirect: "/" }],
path: "/alerting/destination",
component: "./Alerting/destination",
name: "destination",
},
], ],
path: '/alerting/overview',
component: './Alerting/pages/Overview/Overview',
name: 'overview'
},{
routes:[
{ path: '/', redirect: '/' },
],
path: '/alerting/monitor',
component: './Alerting/index',
name: 'monitor'
},{
routes:[
{ path: '/', redirect: '/' },
],
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',
@ -148,12 +142,10 @@ export default [
// ], // ],
// }, // },
{ {
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',
@ -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",
component: "./DataManagement/IndexPatterns",
},
], ],
path: '/data/views/',
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',
@ -247,32 +233,28 @@ 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', path: "/search/alias/rule",
component: './SearchManage/alias/Rule', component: "./SearchManage/alias/Rule",
routes:[ routes: [{ path: "/", redirect: "/" }],
{ path: '/', redirect: '/' }, },
], ],
}
]
}, },
// { // {
// path: '/search/dict', // path: '/search/dict',
@ -354,7 +336,7 @@ export default [
// } // }
//] //]
//}, //},
] ],
}, },
// //
// //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,14 +1,14 @@
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,
@ -16,44 +16,56 @@ class DropdownSelect extends React.Component{
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) => { handleItemClick = (item) => {
let preValue = this.props.value || this.state.value; let preValue = this.props.value || this.state.value;
this.setState({ this.setState(
{
value: item, value: item,
overlayVisible: false, overlayVisible: false,
},()=>{ },
() => {
let onChange = this.props.onChange; let onChange = this.props.onChange;
if(preValue != item && onChange && typeof onChange == 'function'){ if (preValue != item && onChange && typeof onChange == "function") {
onChange(item) onChange(item);
} }
})
} }
);
};
componentDidMount(){ 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,
});
}
} }
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,19 +73,31 @@ 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}
style={{ height: this.props.height }}
>
<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> </div>
<InfiniteScroll <InfiniteScroll
initialLoad={false} initialLoad={false}
@ -82,7 +106,18 @@ class DropdownSelect extends React.Component{
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) && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
匹配不到集群(匹配规则为前缀匹配)
</div>
)}
{(this.state.data || []).map((item) => { {(this.state.data || []).map((item) => {
// return <div className={styles.item}> // return <div className={styles.item}>
// <Button key={item[labelField]} // <Button key={item[labelField]}
@ -92,45 +127,73 @@ class DropdownSelect extends React.Component{
// className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button> // className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
// </div> // </div>
const cstatus = clusterStatus ? clusterStatus[item.id] : null; const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return <DropdownItem key={item.id} return (
<DropdownItem
key={item.id}
isSelected={item.id === value.id} isSelected={item.id === value.id}
clusterItem={item} clusterItem={item}
clusterStatus={cstatus} clusterStatus={cstatus}
onClick={() => { onClick={() => {
this.handleItemClick(item) this.handleItemClick(item);
}} }}
/> />
);
})} })}
</div> </div>
</InfiniteScroll> </InfiniteScroll>
</div> </div>
{!this.state.loading && this.state.hasMore && ( {!this.state.loading && this.state.hasMore && (
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}> <div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
pull load more pull load more
</div> </div>
)} )}
</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}
placement="bottomLeft"
visible={this.state.overlayVisible}
onVisibleChange={(flag) => { onVisibleChange={(flag) => {
this.setState({ overlayVisible: flag }); this.setState({ overlayVisible: flag });
}}> }}
>
{/* <Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}} {/* <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 ? (
<HealthStatusCircle status={cstatus?.health?.status} />
) : (
<Icon
type="close-circle"
style={{
width: 14,
height: 14,
color: "red",
borderRadius: 14,
boxShadow: "0px 0px 5px #555",
}}
/>
)}
</i> </i>
<input className={styles['btn-ds']} style={{outline:'none', paddingLeft:22}} value={value[labelField]} readOnly={true} /> <input
<Icon style={{position:'absolute', top:-6, right:-4}} type="caret-down"/> 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> </span>
</Dropdown>
</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
className={styles.action}
onClick={() => {
const { history, selectedCluster } = this.props; const { history, selectedCluster } = this.props;
// history.push(`/dev_tool`); // history.push(`/dev_tool`);
this.setState({ this.setState({
consoleVisible: !this.state.consoleVisible consoleVisible: !this.state.consoleVisible,
}) });
}}> <Icon type="code" /></a> }}
>
{" "}
<Icon type="code" />
</a>
{/* <NoticeIcon {/* <NoticeIcon
className={styles.action} className={styles.action}
@ -158,8 +173,9 @@ 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={{
display: this.state.consoleVisible ? "block" : "none",
borderTop: "solid 1px #ddd", borderTop: "solid 1px #ddd",
background: "#fff", background: "#fff",
position: "fixed", position: "fixed",
@ -167,7 +183,8 @@ export default class GlobalHeaderRight extends PureComponent {
right: 0, right: 0,
bottom: 0, bottom: 0,
zIndex: 1002, zIndex: 1002,
}}> }}
>
{/* <Resizable {/* <Resizable
defaultSize={{ defaultSize={{
height: '50vh' height: '50vh'
@ -185,18 +202,22 @@ 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 &&
this.props.selectedCluster.id != "" && (
<ConsoleUI
selectedCluster={this.props.selectedCluster}
clusterList={this.props.clusterList} clusterList={this.props.clusterList}
visible={false} visible={false}
minimize={true} minimize={true}
onMinimizeClick={() => { onMinimizeClick={() => {
this.setState({ this.setState({
consoleVisible: false, consoleVisible: false,
}) });
}} }}
clusterStatus={this.props.clusterStatus} clusterStatus={this.props.clusterStatus}
resizeable={true} resizeable={true}
/>} />
)}
</div> </div>
</div> </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(
(e) => {
onChange([...(value || []), e.target.value]); onChange([...(value || []), e.target.value]);
setInputVisible(false); setInputVisible(false);
setInputValue(''); setInputValue("");
}, [value]); },
[value]
);
const handleRemove = useCallback((index) => { const handleRemove = useCallback(
(index) => {
const newValue = [...value]; const newValue = [...value];
newValue.splice(index, 1); newValue.splice(index, 1);
onChange(newValue); onChange(newValue);
}, [value]); },
[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 {
@ -43,17 +49,14 @@ const SendRequestButton = (props: any) => {
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,8 +81,13 @@ 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);
@ -92,32 +100,40 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
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,31 +163,34 @@ 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 }}
data-test-subj="console-application"
className="conApp"
>
<div className="conApp__editor"> <div className="conApp__editor">
<ul className="conApp__autoComplete" id="autocomplete" /> <ul className="conApp__autoComplete" id="autocomplete" />
<EuiFlexGroup <EuiFlexGroup
@ -179,15 +201,17 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
ref={editorActionsRef} ref={editorActionsRef}
> >
<EuiFlexItem> <EuiFlexItem>
<SendRequestButton saveCurrentTextObjectRef={saveCurrentTextObjectRef}/> <SendRequestButton
saveCurrentTextObjectRef={saveCurrentTextObjectRef}
/>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem> <EuiFlexItem>
<ConsoleMenu <ConsoleMenu
getCurl={() => { getCurl={() => {
return editorInstanceRef.current!.getRequestsAsCURL(''); return editorInstanceRef.current!.getRequestsAsCURL("");
}} }}
getDocumentation={() => { getDocumentation={() => {
return getDocumentation(editorInstanceRef.current!, ''); return getDocumentation(editorInstanceRef.current!, "");
}} }}
autoIndent={(event) => { autoIndent={(event) => {
autoIndent(editorInstanceRef.current!, event); autoIndent(editorInstanceRef.current!, event);
@ -202,7 +226,10 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
id={`Editor_${editorInstanceRef.current?.paneKey}`} id={`Editor_${editorInstanceRef.current?.paneKey}`}
className="conApp__editorContent" className="conApp__editorContent"
data-test-subj="request-editor" data-test-subj="request-editor"
onClick={()=>{consoleMenuRef.current?.closePopover(); aceEditorRef.current?.focus()}} onClick={() => {
consoleMenuRef.current?.closePopover();
aceEditorRef.current?.focus();
}}
/> />
</div> </div>
</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,4 +1,4 @@
@import '../../../../../core/public/variables.scss'; @import "../../../../../core/public/variables.scss";
.kbnDocViewerTable { .kbnDocViewerTable {
margin-top: $euiSizeS; margin-top: $euiSizeS;
font-size: 12px; font-size: 12px;
@ -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);
}

View File

@ -1,14 +1,23 @@
import React, {PureComponent, useState} from 'react'; import React, { PureComponent, useState } from "react";
import {connect} from 'dva'; import { connect } from "dva";
import {formatMessage} from 'umi/locale'; import { formatMessage } from "umi/locale";
import {Button, Card, Col, DatePicker, Dropdown, Icon, Input, InputNumber, Row, Select, Statistic} from 'antd'; import {
import moment from 'moment'; Button,
import {DateTime} from 'luxon'; Card,
Col,
DatePicker,
Dropdown,
Icon,
Input,
InputNumber,
Row,
Select,
Statistic,
} from "antd";
import moment from "moment";
import router from "umi/router"; import router from "umi/router";
import numeral from 'numeral'; import "@elastic/charts/dist/theme_only_light.css";
import '@elastic/charts/dist/theme_only_light.css';
import { import {
Axis, Axis,
Chart, Chart,
@ -18,71 +27,34 @@ import {
Position, Position,
ScaleType, ScaleType,
Settings, Settings,
timeFormatter timeFormatter,
} from "@elastic/charts"; } from "@elastic/charts";
import styles from './Metrics.less'; import styles from "./Metrics.less";
import { Spin, Alert } from 'antd'; import { Spin, Alert, Tabs } from "antd";
import {EuiSuperDatePicker} from '@elastic/eui'; import { EuiSuperDatePicker } from "@elastic/eui";
import {calculateBounds} from '../../components/kibana/data/common/query/timefilter'; import { calculateBounds } from "../../components/kibana/data/common/query/timefilter";
import NodeMetric from "./components/node_metric";
import IndexMetric from "./components/index_metric";
import { formatter, getFormatter, getNumFormatter } from "./format";
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
let fetchDataCount = 0; let fetchDataCount = 0;
let tv1 = null; let tv1 = null;
let HealthCircle = (props) => { let HealthCircle = (props) => {
return ( return (
<div style={{ <div
style={{
background: props.color, background: props.color,
width: 12, width: 12,
height: 12, height: 12,
borderRadius: 12, borderRadius: 12,
}}/> }}
) />
} );
};
const unitArr = Array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB");
let 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: timeFormatter(niceTimeFormatByDay(1)),
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'),
}
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
}
}
function getNumFormatter(format, units) {
return (d) => numeral(d).format(format) + (units ? ` ${units}` : '')
}
let timezone = "local"; let timezone = "local";
@ -112,40 +84,45 @@ const theme = {
}, },
axes: { axes: {
axisTitle: { axisTitle: {
fill: '#333', fill: "#333",
fontSize: 12, fontSize: 12,
fontStyle: 'bold', fontStyle: "bold",
fontFamily: "'Open Sans', Helvetica, Arial, sans-serif", fontFamily: "'Open Sans', Helvetica, Arial, sans-serif",
padding: 2, padding: 2,
}, },
axisLine: { axisLine: {
stroke: '#333', stroke: "#333",
}, },
tickLabel: { tickLabel: {
fill: '#333', fill: "#333",
fontSize: 10, fontSize: 10,
fontFamily: "'Open Sans', Helvetica, Arial, sans-serif", fontFamily: "'Open Sans', Helvetica, Arial, sans-serif",
fontStyle: 'normal', fontStyle: "normal",
padding: 2, padding: 2,
}, },
tickLine: { tickLine: {
visible: true, visible: true,
stroke: '#333', stroke: "#333",
strokeWidth: 1, strokeWidth: 1,
padding: 0, padding: 0,
}, },
}, },
} };
const vstyle = { const vstyle = {
fontSize: 12, fontSize: 12,
wordBreak: "break-all", wordBreak: "break-all",
marginRight: "5px" marginRight: "5px",
}; };
const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading}) => { const MonitorDatePicker = ({
timeRange,
commonlyUsedRanges,
onChange,
isLoading,
}) => {
// const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]); // const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]);
const [isPaused, setIsPaused] = useState(true); const [isPaused, setIsPaused] = useState(false);
const [refreshInterval, setRefreshInterval] = useState(10000); const [refreshInterval, setRefreshInterval] = useState(10000);
const onTimeChange = ({ start, end }) => { const onTimeChange = ({ start, end }) => {
@ -156,7 +133,7 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
}; };
const onRefresh = ({ start, end, refreshInterval }) => { const onRefresh = ({ start, end, refreshInterval }) => {
onChange({start, end}) onChange({ start, end });
}; };
const onRefreshChange = ({ isPaused, refreshInterval }) => { const onRefreshChange = ({ isPaused, refreshInterval }) => {
@ -166,7 +143,7 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
return ( return (
<EuiSuperDatePicker <EuiSuperDatePicker
dateFormat= '' dateFormat=""
isLoading={isLoading} isLoading={isLoading}
start={timeRange?.min} start={timeRange?.min}
end={timeRange?.max} end={timeRange?.max}
@ -184,40 +161,40 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
@connect(({ clusterMonitor, global }) => ({ @connect(({ clusterMonitor, global }) => ({
clusterMonitor, clusterMonitor,
selectedCluster: global.selectedCluster, selectedCluster: global.selectedCluster,
clusterList: global.clusterList clusterList: global.clusterList,
})) }))
class ClusterMonitor extends PureComponent { class ClusterMonitor extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
//this.timePicker = React.createRef(); //this.timePicker = React.createRef();
this.handleChartBrush = this.handleChartBrush.bind(this); this.handleChartBrush = this.handleChartBrush.bind(this);
} this.chartRefs = [];
this.handleTimeChange = this.handleTimeChange.bind(this);
state = { this.state = {
spinning: false, spinning: false,
clusterID: null, clusterID: null,
timeRange: { timeRange: {
min: 'now-1h', //moment().subtract(1, 'h').toISOString(), min: "now-1h", //moment().subtract(1, 'h').toISOString(),
max: 'now'//moment().toISOString() max: "now", //moment().toISOString()
timeFormatter: formatter.dates(1),
}, },
};
} }
componentWillReceiveProps(newProps) { componentWillReceiveProps(newProps) {}
}
fetchData = () => { fetchData = () => {
if (
console.log("fetching data ing."+this.state.clusterID) this.state.clusterID === undefined ||
this.state.clusterID === "" ||
if (this.state.clusterID===undefined||this.state.clusterID===""||this.state.clusterID===null){ this.state.clusterID === null
return ) {
return;
} }
this.setState({ this.setState({
spinning: true, spinning: true,
}) });
fetchDataCount++; fetchDataCount++;
const { dispatch } = this.props; const { dispatch } = this.props;
@ -227,32 +204,30 @@ class ClusterMonitor extends PureComponent {
to: timeRange.max, to: timeRange.max,
}); });
dispatch({ dispatch({
type: 'clusterMonitor/fetchClusterMetrics', type: "clusterMonitor/fetchClusterMetrics",
payload: { payload: {
timeRange: { timeRange: {
min: bounds.min.valueOf(), min: bounds.min.valueOf(),
max: bounds.max.valueOf(), max: bounds.max.valueOf(),
}, },
cluster_id:this.state.clusterID cluster_id: this.state.clusterID,
}, },
}).then(()=>{ }).then((res) => {
this.chartRefs = Object.keys(res?.metrics || {}).map(() => {
return React.createRef();
});
this.setState({ this.setState({
spinning: false, spinning: false,
}) });
}) });
} };
componentWillUnmount() {
clearInterval(tv1);
}
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
// console.log(this.props.selectedCluster) // console.log(this.props.selectedCluster)
// console.log(this.state.clusterID) // console.log(this.state.clusterID)
if (this.props.selectedCluster.id !== this.state.clusterID) { if (this.props.selectedCluster.id !== this.state.clusterID) {
console.log("cluster changed") console.log("cluster changed");
this.setState({ clusterID: this.props.selectedCluster.id }, () => { this.setState({ clusterID: this.props.selectedCluster.id }, () => {
//TODO 处理 cancel 事件,先把当前还在执行中的请求结束,避免更新完成之后,被晚到的之前的请求给覆盖了。 //TODO 处理 cancel 事件,先把当前还在执行中的请求结束,避免更新完成之后,被晚到的之前的请求给覆盖了。
@ -261,41 +236,52 @@ class ClusterMonitor extends PureComponent {
} }
} }
pointerUpdate = (event) => {
this.chartRefs.forEach((ref) => {
if (ref.current) {
ref.current.dispatchExternalPointerEvent(event);
}
});
};
componentDidMount() { componentDidMount() {
const { match, location } = this.props; const { match, location } = this.props;
const queryESID = this.props.match.params.cluster_id; const queryESID = this.props.match.params.cluster_id;
if (queryESID !== null && queryESID !== undefined) { if (queryESID !== null && queryESID !== undefined) {
this.state.clusterID=queryESID this.state.clusterID = queryESID;
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: 'global/changeClusterById', type: "global/changeClusterById",
payload: { payload: {
id: queryESID id: queryESID,
} },
})
}else if (this.props.selectedCluster.id!==undefined&&this.props.selectedCluster.id!==null){
this.setState({ clusterID:this.props.selectedCluster.id }, () => {
}); });
} else if (
this.props.selectedCluster.id !== undefined &&
this.props.selectedCluster.id !== null
) {
this.setState({ clusterID: this.props.selectedCluster.id }, () => {});
} else { } else {
//alert("cluster ID is not set"); //alert("cluster ID is not set");
return return;
} }
console.log("selectedCluster:"+this.state.clusterID)
let min = location.query.start || this.state.timeRange.min; //'2020-12-10 15:00'; let min = location.query.start || this.state.timeRange.min; //'2020-12-10 15:00';
let max = location.query.end || this.state.timeRange.max; //'2020-12-10 16:00'; let max = location.query.end || this.state.timeRange.max; //'2020-12-10 16:00';
this.setState({ this.setState(
{
timeRange: { timeRange: {
...this.state.timeRange,
min: min, min: min,
max: max, max: max,
}, },
}, () => { },
() => {
this.fetchData(); this.fetchData();
}) }
);
// tv1 = setInterval(()=>{ // tv1 = setInterval(()=>{
// this.fetchData(); // this.fetchData();
@ -312,7 +298,6 @@ class ClusterMonitor extends PureComponent {
}, durationInSeconds * 1000); }, durationInSeconds * 1000);
} }
handleChartBrush({ x }) { handleChartBrush({ x }) {
if (!x) { if (!x) {
return; return;
@ -321,28 +306,44 @@ class ClusterMonitor extends PureComponent {
let timeRange = { let timeRange = {
min: moment(from).toISOString(), min: moment(from).toISOString(),
max: moment(to).toISOString(), max: moment(to).toISOString(),
} };
this.setState({ timeRange.day = moment
.duration(moment(to).valueOf() - moment(from).valueOf())
.asDays();
this.setState(
{
timeRange: timeRange, timeRange: timeRange,
}, () => { },
() => {
this.fetchData(); this.fetchData();
}); }
);
} }
handleTimeChange = ({ start, end }) => { handleTimeChange = ({ start, end }) => {
this.setState({ const bounds = calculateBounds({
from: start,
to: end,
});
const day = moment
.duration(bounds.max.valueOf() - bounds.min.valueOf())
.asDays();
const intDay = parseInt(day) + 1;
this.setState(
{
timeRange: { timeRange: {
min: start, min: start,
max: end, max: end,
} timeFormatter: formatter.dates(intDay),
},()=>{ },
},
() => {
this.fetchData(); this.fetchData();
})
} }
);
};
render() { render() {
const { clusterMonitor } = this.props; const { clusterMonitor } = this.props;
let clusterStats = {}; let clusterStats = {};
let clusterMetrics = {}; let clusterMetrics = {};
@ -371,57 +372,56 @@ class ClusterMonitor extends PureComponent {
clusterMetrics = clusterMonitor.metrics; clusterMetrics = clusterMonitor.metrics;
} }
const commonlyUsedRanges = const commonlyUsedRanges = [
[
{ {
from: 'now-15m', from: "now-15m",
to: 'now', to: "now",
display: '最近15分钟' display: "最近15分钟",
}, },
{ {
from: 'now-30m', from: "now-30m",
to: 'now', to: "now",
display: '最近30分钟' display: "最近30分钟",
}, },
{ {
from: 'now-1h', from: "now-1h",
to: 'now', to: "now",
display: '最近一小时' display: "最近一小时",
}, },
{ {
from: 'now-24h', from: "now-24h",
to: 'now', to: "now",
display: '最近一天', display: "最近一天",
}, },
{ {
from: 'now/d', from: "now/d",
to: 'now/d', to: "now/d",
display:'今天', display: "今天",
}, },
{ {
from: 'now/w', from: "now/w",
to: 'now/w', to: "now/w",
display: '这个星期' display: "这个星期",
}, },
{ {
from: 'now-7d', from: "now-7d",
to: 'now', to: "now",
display: '最近一周', display: "最近一周",
}, },
{ {
from: 'now-30d', from: "now-30d",
to: 'now', to: "now",
display: '最近一个月', display: "最近一个月",
}, },
{ {
from: 'now-90d', from: "now-90d",
to: 'now', to: "now",
display: '最近三个月', display: "最近三个月",
}, },
{ {
from: 'now-1y', from: "now-1y",
to: 'now', to: "now",
display: '最近一年', display: "最近一年",
}, },
].map(({ from, to, display }) => { ].map(({ from, to, display }) => {
return { return {
@ -433,123 +433,235 @@ class ClusterMonitor extends PureComponent {
return ( return (
<Spin spinning={this.state.spinning} tip="Loading..."> <Spin spinning={this.state.spinning} tip="Loading...">
<div> <div style={{ background: "#fff" }}>
<div style={{ background: "#fff", padding: "5px", marginBottom: 5 }}> <div style={{ background: "#fff", padding: "5px", marginBottom: 5 }}>
<MonitorDatePicker timeRange={this.state.timeRange} commonlyUsedRanges={commonlyUsedRanges} <MonitorDatePicker
timeRange={this.state.timeRange}
commonlyUsedRanges={commonlyUsedRanges}
isLoading={this.state.spinning} isLoading={this.state.spinning}
onChange={this.handleTimeChange} /> onChange={this.handleTimeChange}
/>
</div> </div>
<Card <div
//title={this.state.clusterID?this.state.clusterID:''} style={{
style={{marginBottom: 5}}> padding: 15,
<Row> borderTop: "1px solid rgb(232, 232, 232)",
borderBottom: "1px solid rgb(232, 232, 232)",
}}
>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="集群名称" value={clusterStats.cluster_name}/> <Statistic
valueStyle={vstyle}
title="集群名称"
value={clusterStats.cluster_name}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="在线时长" value={clusterStats.uptime}/> <Statistic
valueStyle={vstyle}
title="在线时长"
value={clusterStats.uptime}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="集群版本" value={clusterStats.version}/> <Statistic
valueStyle={vstyle}
title="集群版本"
value={clusterStats.version}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="健康情况" value={clusterStats.status} <Statistic
prefix={<HealthCircle color={clusterStats.status}/>}/> valueStyle={vstyle}
title="健康情况"
value={clusterStats.status}
prefix={<HealthCircle color={clusterStats.status} />}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="节点数" value={clusterStats.nodes_count}/> <Statistic
valueStyle={vstyle}
title="节点数"
value={clusterStats.nodes_count}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="索引数" value={clusterStats.indices_count}/> <Statistic
valueStyle={vstyle}
title="索引数"
value={clusterStats.indices_count}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="主/总分片" <Statistic
value={clusterStats.primary_shards + '/' + clusterStats.total_shards}/> valueStyle={vstyle}
title="主/总分片"
value={
clusterStats.primary_shards +
"/" +
clusterStats.total_shards
}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="未分配分片" <Statistic
value={clusterStats.unassigned_shards}/> valueStyle={vstyle}
title="未分配分片"
value={clusterStats.unassigned_shards}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="文档数" value={clusterStats.document_count}/> <Statistic
valueStyle={vstyle}
title="文档数"
value={clusterStats.document_count}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="存储空间" <Statistic
value={clusterStats.used_store_bytes + '/' + clusterStats.max_store_bytes}/> valueStyle={vstyle}
title="存储空间"
value={
clusterStats.used_store_bytes +
"/" +
clusterStats.max_store_bytes
}
/>
</Col> </Col>
<Col md={2} xs={4}> <Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="JVM 内存" <Statistic
value={clusterStats.used_jvm_bytes + '/' + clusterStats.max_jvm_bytes}/> valueStyle={vstyle}
title="JVM 内存"
value={
clusterStats.used_jvm_bytes +
"/" +
clusterStats.max_jvm_bytes
}
/>
</Col> </Col>
</Row> </Row>
</Card> </div>
<div>
<Tabs animated={false}>
{ <Tabs.TabPane key="cluster" tab="Cluster">
Object.keys(clusterMetrics).map((e, i) => { {Object.keys(clusterMetrics).map((e, i) => {
let axis = clusterMetrics[e].axis let axis = clusterMetrics[e].axis;
let lines = clusterMetrics[e].lines let lines = clusterMetrics[e].lines;
let disableHeaderFormat = false let disableHeaderFormat = false;
let headerUnit = "" let headerUnit = "";
return ( return (
<div key={e} className={styles.vizChartContainer}> <div key={e} className={styles.vizChartContainer}>
<Chart size={[, 200]} className={styles.vizChartItem}> <Chart
<Settings theme={theme} showLegend legendPosition={Position.Top} size={[, 200]}
className={styles.vizChartItem}
ref={this.chartRefs[i]}
>
<Settings
pointerUpdateDebounce={0}
pointerUpdateTrigger="x"
externalPointerEvents={{
tooltip: { visible: true },
}}
onPointerUpdate={this.pointerUpdate}
theme={theme}
showLegend
legendPosition={Position.Top}
onBrushEnd={this.handleChartBrush} onBrushEnd={this.handleChartBrush}
tooltip={{ tooltip={{
headerFormatter: disableHeaderFormat headerFormatter: disableHeaderFormat
? undefined ? undefined
: ({value}) => `${formatter.full_dates(value)}${headerUnit ? ` ${headerUnit}` : ''}`, : ({ value }) =>
`${formatter.full_dates(value)}${
headerUnit ? ` ${headerUnit}` : ""
}`,
}} }}
debug={false}/> debug={false}
<Axis id="{e}-bottom" position={Position.Bottom} showOverlappingTicks
labelFormat={formatter.dates}
tickFormat={formatter.dates}
/> />
{ <Axis
axis.map((item) => { id="{e}-bottom"
return <Axis key={e + '-' + item.id} position={Position.Bottom}
id={e + '-' + item.id} showOverlappingTicks
labelFormat={this.state.timeRange.timeFormatter}
tickFormat={this.state.timeRange.timeFormatter}
/>
{axis.map((item) => {
return (
<Axis
key={e + "-" + item.id}
id={e + "-" + item.id}
showGridLines={item.showGridLines} showGridLines={item.showGridLines}
groupId={item.group} groupId={item.group}
title={formatMessage({id: 'dashboard.charts.title.' + e + '.axis.' + item.title})} title={formatMessage({
id:
"dashboard.charts.title." +
e +
".axis." +
item.title,
})}
position={item.position} position={item.position}
ticks={item.ticks} ticks={item.ticks}
labelFormat={getFormatter(item.formatType, item.labelFormat)} labelFormat={getFormatter(
tickFormat={getFormatter(item.formatType, item.tickFormat)} item.formatType,
item.labelFormat
)}
tickFormat={getFormatter(
item.formatType,
item.tickFormat
)}
/> />
}) );
} })}
{ {lines.map((item) => {
lines.map((item) => { return (
return <LineSeries key={item.metric.label} <LineSeries
key={item.metric.label}
id={item.metric.label} id={item.metric.label}
groupId={item.metric.group} groupId={item.metric.group}
timeZone={timezone} timeZone={timezone}
xScaleType={ScaleType.Time} xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear}
xAccessor={0} xAccessor={0}
tickFormat={getFormatter(item.metric.formatType, item.metric.tickFormat, item.metric.units)} tickFormat={getFormatter(
item.metric.formatType,
item.metric.tickFormat,
item.metric.units
)}
yAccessors={[1]} yAccessors={[1]}
data={item.data} data={item.data}
curve={CurveType.CURVE_MONOTONE_X} curve={CurveType.CURVE_MONOTONE_X}
/> />
}) );
} })}
</Chart> </Chart>
</div> </div>
) );
}) })}
} </Tabs.TabPane>
<Tabs.TabPane key="node" tab="Node">
<NodeMetric
clusterID={this.props.selectedCluster.id}
timezone={timezone}
timeRange={this.state.timeRange}
handleTimeChange={this.handleTimeChange}
/>
</Tabs.TabPane>
<Tabs.TabPane key="index" tab="Index">
<IndexMetric
clusterID={this.props.selectedCluster.id}
timezone={timezone}
timeRange={this.state.timeRange}
handleTimeChange={this.handleTimeChange}
/>
</Tabs.TabPane>
</Tabs>
</div>
</div> </div>
</Spin> </Spin>
); );

View File

@ -1,6 +1,6 @@
.vizChartContainer { .vizChartContainer {
padding: 0px; padding: 0px;
border-bottom: 1px solid rgb(232, 232, 232);
} }
.vizChartItem { .vizChartItem {

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 {
getClusterOverview,
getClusterNodeStats,
getClusterList,
} from "@/services/dashboard";
import { getClusterMetrics } from "@/services/cluster"; import { getClusterMetrics } from "@/services/cluster";
export default { export default {
namespace: 'clusterMonitor', namespace: "clusterMonitor",
state: { state: {},
},
effects: { effects: {
*fetchClusterOverview({ payload, callback }, { call, put }) { *fetchClusterOverview({ payload, callback }, { call, put }) {
let clusterData = yield call(getClusterOverview, payload); let clusterData = yield call(getClusterOverview, payload);
yield put({type: 'saveData', payload: clusterData}) yield put({ type: "saveData", payload: clusterData });
if(callback && typeof callback == 'function'){ if (callback && typeof callback == "function") {
callback(clusterData); callback(clusterData);
} }
}, },
*fetchClusterMetrics({ payload, callback }, { call, put }) { *fetchClusterMetrics({ payload, callback }, { call, put }) {
let clusterMetrics = yield call(getClusterMetrics, payload); let clusterMetrics = yield call(getClusterMetrics, payload);
yield put({type: 'saveData', payload: clusterMetrics}) yield put({ type: "saveData", payload: clusterMetrics });
if(callback && typeof callback == 'function'){ if (callback && typeof callback == "function") {
callback(clusterMetrics); callback(clusterMetrics);
console.log("finished call:" + params.cluster_id); console.log("finished call:" + params.cluster_id);
} }
return clusterMetrics;
}, },
*fetchClusterNodeStats({ callback }, { call, put }) { *fetchClusterNodeStats({ callback }, { call, put }) {
let nodesStats = yield call(getClusterNodeStats); let nodesStats = yield call(getClusterNodeStats);
//yield put({type: 'saveData', payload: nodesStats}) //yield put({type: 'saveData', payload: nodesStats})
if(callback && typeof callback == 'function'){ if (callback && typeof callback == "function") {
callback(nodesStats); callback(nodesStats);
} }
}, },
*fetchClusterList({ callback }, { call, put }) { *fetchClusterList({ callback }, { call, put }) {
let clusterData = yield call(getClusterList); let clusterData = yield call(getClusterList);
yield put({type: 'saveData', payload: { yield put({
clusterList: clusterData type: "saveData",
}}) payload: {
if(callback && typeof callback == 'function'){ clusterList: clusterData,
},
});
if (callback && typeof callback == "function") {
callback(clusterData); callback(clusterData);
} }
} },
}, },
reducers: { reducers: {
saveData(state, { payload }) { saveData(state, { payload }) {
return { return {
...state, ...state,
...payload ...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,18 +1,18 @@
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 = {
@ -24,13 +24,21 @@ class NewTabMenu extends React.Component{
dataSourceKey: 1, dataSourceKey: 1,
selectedIndex: -1, selectedIndex: -1,
overlayVisible: false, overlayVisible: false,
} };
} }
componentDidMount(){ 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,
});
}
} }
handleInfiniteOnLoad = (current) => { handleInfiniteOnLoad = (current) => {
let { size } = this.props; let { size } = this.props;
@ -38,20 +46,19 @@ class NewTabMenu extends React.Component{
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,9 +67,8 @@ 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;
@ -71,8 +77,8 @@ class NewTabMenu extends React.Component{
// const item = data[selectedIndex]; // const item = data[selectedIndex];
this.setState({ this.setState({
selectedIndex, selectedIndex,
}) });
} };
onKeyDown = (e) => { onKeyDown = (e) => {
const { which } = e; const { which } = e;
@ -82,33 +88,49 @@ class NewTabMenu extends React.Component{
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 = (<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 }}>
onMouseEnter={()=>{this.searchInputRef.focus()}} <div
tabIndex="0" onKeyDown={this.onKeyDown}> className={styles.infiniteContainer}
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}> style={{ height: this.props.height }}
<input className={styles['btn-ds']} style={{outline:'none'}} onMouseEnter={() => {
ref={(ref)=>{this.searchInputRef= ref;}} this.searchInputRef.focus();
onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} /> }}
tabIndex="0"
onKeyDown={this.onKeyDown}
>
<div
className={styles.filter}
style={{ paddingTop: 10, paddingBottom: 0 }}
>
<input
className={styles["btn-ds"]}
style={{ outline: "none" }}
ref={(ref) => {
this.searchInputRef = ref;
}}
onChange={this.handleInputChange}
placeholder="输入集群名称查找"
value={this.state.displayValue || ""}
/>
</div> </div>
<InfiniteScroll <InfiniteScroll
initialLoad={this.state.initialLoad} initialLoad={this.state.initialLoad}
@ -117,40 +139,57 @@ class NewTabMenu extends React.Component{
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) && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
匹配不到集群(匹配规则为前缀匹配)
</div>
)}
{(this.state.data || []).map((item, idx) => { {(this.state.data || []).map((item, idx) => {
const cstatus = clusterStatus ? clusterStatus[item.id] : null; const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return <DropdownItem key={item.id} return (
<DropdownItem
key={item.id}
clusterItem={item} clusterItem={item}
clusterStatus={cstatus} clusterStatus={cstatus}
isSelected={this.state.selectedIndex == idx} isSelected={this.state.selectedIndex == idx}
onClick={() => { onClick={() => {
this.handleItemClick(item) this.handleItemClick(item);
}} }}
/> />
);
})} })}
</div> </div>
</InfiniteScroll> </InfiniteScroll>
</div> </div>
{!this.state.loading && this.state.hasMore && ( {!this.state.loading && this.state.hasMore && (
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}> <div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
pull load more pull load more
</div> </div>
)} )}
</div>); </div>
);
return ( return (
<div> <div>
<Dropdown overlay={menu} placement="bottomLeft" <Dropdown
overlay={menu}
placement="bottomLeft"
visible={this.state.overlayVisible} visible={this.state.overlayVisible}
onVisibleChange={(flag) => { onVisibleChange={(flag) => {
this.setState({ overlayVisible: flag }); this.setState({ overlayVisible: flag });
}}> }}
>
{this.props.children} {this.props.children}
</Dropdown> </Dropdown>
</div> </div>
) );
} }
} }
export default NewTabMenu; export default NewTabMenu;

View File

@ -1,71 +1,76 @@
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig, import {
tryConnect} from "@/services/cluster"; createClusterConfig,
searchClusterConfig,
updateClusterConfig,
deleteClusterConfig,
tryConnect,
} from "@/services/cluster";
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: '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;
}); });
@ -73,94 +78,94 @@ export default {
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 {
@ -126,34 +52,41 @@ class Index extends PureComponent {
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
onClick={() => {
this.setState({ this.setState({
editingCommand: record, editingCommand: record,
drawerVisible: true, 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) { buildRawCommonCommandRequest(cmd) {
const { requests } = cmd; const { requests } = cmd;
if (!requests) { if (!requests) {
return ''; 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,7 +230,9 @@ 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>
@ -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,41 +253,78 @@ 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 }}
>
<div>标题</div>
<div>
<Input
value={editingCommand.title}
onChange={this.onEditTitleChange}
style={{ width: 250 }}
/>
</div>
</div>
<div
style={{ display: "flex", alignItems: "center", marginBottom: 15 }}
>
<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" height="300px"
language="text" language="text"
theme="light" theme="light"
@ -319,8 +337,15 @@ class Index extends PureComponent {
tabSize: 2, tabSize: 2,
wordBasedSuggestions: true, wordBasedSuggestions: true,
}} }}
onMount={(editor)=>this.handleEditorDidMount('indexSettingsEditor', editor)} 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,45 +1,54 @@
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;
},
*updateCommand({ payload }, { call, put, select }) {
let res = yield call(saveCommand, payload);
if (res.error) {
message.error(res.error);
return false;
} }
} // let {data, total} = yield select(state => state.command);
})
return res; return res;
}, },
}, },
@ -48,7 +57,7 @@ export default {
return { return {
...state, ...state,
...payload, ...payload,
} };
} },
} },
} };

View File

@ -1,10 +1,10 @@
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,
}); });
@ -12,13 +12,21 @@ export async function searchCommand(params) {
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,
}); });
} }