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"
)
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{}{
}
reqParams := elastic.CommonCommand{}
err := h.DecodeJSON(req, &reqParams)
if err != nil {
resBody["error"] = err
h.WriteJSON(w, resBody, http.StatusInternalServerError)
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
@ -28,20 +28,25 @@ func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *h
reqParams.ID = util.GetUUID()
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)
searchRes, err := esClient.SearchWithRawQueryDSL(indexName, queryDSL)
if err != nil {
resBody["error"] = err
h.WriteJSON(w, resBody, http.StatusInternalServerError)
resBody["error"] = err.Error()
h.WriteJSON(w, resBody, http.StatusOK)
return
}
if len(searchRes.Hits.Hits) > 0 {
resBody["error"] = "title already exists"
h.WriteJSON(w, resBody, http.StatusInternalServerError)
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"] = "created"
@ -49,20 +54,64 @@ func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *h
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) {
resBody := map[string]interface{}{
}
var (
title = h.GetParameterOrDefault(req, "title", "")
queryDSL = `{"query":{"bool":{"filter":[%s]}}, "size": %d, "from": %d}`
keyword = h.GetParameterOrDefault(req, "keyword", "")
queryDSL = `{"query":{"bool":{"must":[%s]}}, "size": %d, "from": %d}`
strSize = h.GetParameterOrDefault(req, "size", "20")
strFrom = h.GetParameterOrDefault(req, "from", "0")
filterBuilder = &strings.Builder{}
)
if title != ""{
filterBuilder.WriteString(fmt.Sprintf(`{"prefix":{"title": "%s"}}`, title))
if keyword != ""{
filterBuilder.WriteString(fmt.Sprintf(`{"query_string": {
"default_field": "*",
"query": "%s"
}
}`, keyword))
}
size, _ := strconv.Atoi(strSize)
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.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.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.Alert{}, "alerting-alerts")
orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history")
orm.RegisterSchema(elastic.CommonCommand{})
alertSrv.GetScheduler().Start()
},nil){
app.Run()

View File

@ -1,28 +1,28 @@
export default [
// user
{
path: '/user',
component: '../layouts/UserLayout',
path: "/user",
component: "../layouts/UserLayout",
routes: [
{ path: '/user', redirect: '/user/login' },
{ path: '/user/login', component: './User/Login' },
{ path: '/user/register', component: './User/Register' },
{ path: '/user/register-result', component: './User/RegisterResult' },
{ path: "/user", redirect: "/user/login" },
{ path: "/user/login", component: "./User/Login" },
{ path: "/user/register", component: "./User/Register" },
{ path: "/user/register-result", component: "./User/RegisterResult" },
],
},
// app
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
authority: ['admin', 'user'],
path: "/",
component: "../layouts/BasicLayout",
Routes: ["src/pages/Authorized"],
authority: ["admin", "user"],
routes: [
// cluster
{ path: '/', redirect: '/cluster/overview' },
{ path: "/", redirect: "/cluster/overview" },
{
path: '/cluster',
name: 'cluster',
icon: 'cluster',
path: "/cluster",
name: "cluster",
icon: "cluster",
routes: [
// { path: '/', redirect: '/platform/gateway' },
// {
@ -34,33 +34,29 @@ export default [
// ],
// },
{
path: '/cluster/overview',
name: 'overview',
component: './Cluster/NewOverview',
path: "/cluster/overview",
name: "overview",
component: "./Cluster/NewOverview",
// hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
routes: [{ path: "/", redirect: "/" }],
},
{
path: '/cluster/monitoring/:cluster_id',
name: 'cluster',
component: './Cluster/ClusterMonitor',
path: "/cluster/monitoring/:cluster_id",
name: "cluster",
component: "./Cluster/ClusterMonitor",
hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
}, {
path: '/cluster/metrics/',
name: 'monitoring',
component: './Cluster/Metrics',
routes:[
{ path: '/', redirect: '/' },
],
}, {
path: '/cluster/metrics/:cluster_id',
name: 'monitoring',
component: './Cluster/Metrics',
routes: [{ path: "/", redirect: "/" }],
},
{
path: "/cluster/metrics/",
name: "monitoring",
component: "./Cluster/Metrics",
routes: [{ path: "/", redirect: "/" }],
},
{
path: "/cluster/metrics/:cluster_id",
name: "monitoring",
component: "./Cluster/Metrics",
hideInMenu: true,
},
// {
@ -91,7 +87,7 @@ export default [
// }
// ]
// },
]
],
},
//devtools
// {
@ -106,38 +102,36 @@ export default [
//alerting
{
path: '/alerting',
name: 'alerting',
icon: 'alert',
routes: [{
path: "/alerting",
name: "alerting",
icon: "alert",
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
{
path: '/data',
name: 'data',
icon: 'database',
path: "/data",
name: "data",
icon: "database",
routes: [
// {
// path: '/data/overview',
@ -148,12 +142,10 @@ export default [
// ],
// },
{
path: '/data/index',
name: 'index',
component: './DataManagement/Index',
routes:[
{ path: '/', redirect: '/' },
],
path: "/data/index",
name: "index",
component: "./DataManagement/Index",
routes: [{ path: "/", redirect: "/" }],
},
// {
// path: '/data/document',
@ -180,31 +172,25 @@ export default [
// ],
// },
{
routes:[
{ path: '/', redirect: '/' },
],
path: '/data/discover',
name: 'discover',
component: './DataManagement/Discover',
routes: [{ path: "/", redirect: "/" }],
path: "/data/discover",
name: "discover",
component: "./DataManagement/Discover",
},
{
routes:[
{ path: '/', redirect: '/' },
routes: [{ path: "/", redirect: "/" }],
path: "/data/views/",
name: "indexPatterns",
component: "./DataManagement/IndexPatterns",
},
],
path: '/data/views/',
name: 'indexPatterns',
component: './DataManagement/IndexPatterns',
},
]
},
//search
{
path: '/search',
name: 'search',
icon: 'search',
path: "/search",
name: "search",
icon: "search",
routes: [
// {
// path: '/search/overview',
@ -247,32 +233,28 @@ export default [
// ]
// },
{
path: '/search/alias',
name: 'alias',
component: './SearchManage/alias/Alias',
path: "/search/alias",
name: "alias",
component: "./SearchManage/alias/Alias",
routes: [
{
path: '/search/alias',
redirect: '/search/alias/index',
path: "/search/alias",
redirect: "/search/alias/index",
// routes:[
// { path: '/', redirect: '/' },
// ],
},
{
path: '/search/alias/index',
component: './SearchManage/alias/AliasManage',
routes:[
{ path: '/', redirect: '/' },
],
path: "/search/alias/index",
component: "./SearchManage/alias/AliasManage",
routes: [{ path: "/", redirect: "/" }],
},
{
path: '/search/alias/rule',
component: './SearchManage/alias/Rule',
routes:[
{ path: '/', redirect: '/' },
path: "/search/alias/rule",
component: "./SearchManage/alias/Rule",
routes: [{ path: "/", redirect: "/" }],
},
],
}
]
},
// {
// path: '/search/dict',
@ -354,7 +336,7 @@ export default [
// }
//]
//},
]
],
},
//
// //sync
@ -450,32 +432,32 @@ export default [
//settings
{
path: '/system',
name: 'system',
icon: 'setting',
path: "/system",
name: "system",
icon: "setting",
routes: [
{
path: '/system/cluster',
name: 'cluster',
component: './System/Cluster/Index',
path: "/system/cluster",
name: "cluster",
component: "./System/Cluster/Index",
},
{
path: '/system/cluster/regist',
name: 'registCluster',
component: './System/Cluster/Step',
hideInMenu: true
path: "/system/cluster/regist",
name: "registCluster",
component: "./System/Cluster/Step",
hideInMenu: true,
},
{
path: '/system/cluster/edit',
name: 'editCluster',
component: './System/Cluster/Form',
hideInMenu: true
path: "/system/cluster/edit",
name: "editCluster",
component: "./System/Cluster/Form",
hideInMenu: true,
},
{
path: '/system/command',
name: 'commonCommand',
component: './System/Command/Index',
hideInMenu: true
path: "/system/command",
name: "commonCommand",
component: "./System/Command/Index",
// hideInMenu: true
},
// {
// path: '/system/settings',
@ -549,7 +531,7 @@ export default [
// },
// ]
// },
]
],
},
// // 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 React from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import styles from './DropdownSelect.less';
import { Button, Dropdown, List, Spin, message, Icon, Input } from "antd";
import React from "react";
import InfiniteScroll from "react-infinite-scroller";
import styles from "./DropdownSelect.less";
import _ from "lodash";
import {DropdownItem} from './DropdownItem';
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
import { DropdownItem } from "./DropdownItem";
import { HealthStatusCircle } from "@/components/infini/health_status_circle";
class DropdownSelect extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
value: props.defaultValue,
loading: false,
@ -16,44 +16,56 @@ class DropdownSelect extends React.Component{
overlayVisible: false,
data: (props.data || []).slice(0, props.size),
dataSource: [...props.data],
}
};
}
handleItemClick = (item) => {
let preValue = this.props.value || this.state.value;
this.setState({
this.setState(
{
value: item,
overlayVisible: false,
},()=>{
},
() => {
let onChange = this.props.onChange;
if(preValue != item && onChange && typeof onChange == 'function'){
onChange(item)
if (preValue != item && onChange && typeof onChange == "function") {
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) => {
let { size } = this.props;
let targetLength = current * size;
let { hasMore, dataSource } = this.state;
if (dataSource.length < targetLength) {
targetLength = dataSource.length;
hasMore = false
hasMore = false;
}
const newData = this.state.dataSource.slice(0, targetLength);
this.setState({
data: newData,
hasMore: hasMore,
})
}
});
};
handleInputChange = (e) => {
const name = e.target.value;
const newData = this.props.data.filter(item=>{
const newData = this.props.data.filter((item) => {
return item.name.includes(name);
});
this.setState({
@ -61,19 +73,31 @@ class DropdownSelect extends React.Component{
dataSource: newData,
data: newData,
hasMore: newData.length > this.props.size,
})
}
});
};
render() {
let me = this;
const { labelField, clusterStatus } = this.props;
let value = this.props.value || this.state.value;
let displayVaue = value[labelField];
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
<div 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||''} />
const menu = (
<div className={styles.dropmenu} style={{ width: this.props.width }}>
<div
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>
<InfiniteScroll
initialLoad={false}
@ -82,7 +106,18 @@ class DropdownSelect extends React.Component{
useWindow={false}
>
<div className={styles.dslist}>
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
{(!this.state.data || !this.state.data.length) && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
匹配不到集群(匹配规则为前缀匹配)
</div>
)}
{(this.state.data || []).map((item) => {
// return <div className={styles.item}>
// <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>
// </div>
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return <DropdownItem key={item.id}
return (
<DropdownItem
key={item.id}
isSelected={item.id === value.id}
clusterItem={item}
clusterStatus={cstatus}
onClick={() => {
this.handleItemClick(item)
this.handleItemClick(item);
}}
/>
);
})}
</div>
</InfiniteScroll>
</div>
{!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
</div>
)}
</div>);
</div>
);
const cstatus = clusterStatus ? clusterStatus[value?.id] : null;
return(
this.props.visible ?
(<Dropdown overlay={menu} placement="bottomLeft" visible={this.state.overlayVisible}
return this.props.visible ? (
<Dropdown
overlay={menu}
placement="bottomLeft"
visible={this.state.overlayVisible}
onVisibleChange={(flag) => {
this.setState({ overlayVisible: flag });
}}>
}}
>
{/* <Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}}
type="caret-down"/></Button> */}
<span style={{position:'relative'}}>
<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'}} />}
<span style={{ position: "relative" }}>
<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",
}}
/>
)}
</i>
<input className={styles['btn-ds']} style={{outline:'none', paddingLeft:22}} value={value[labelField]} readOnly={true} />
<Icon style={{position:'absolute', top:-6, right:-4}} type="caret-down"/>
<input
className={styles["btn-ds"]}
style={{ outline: "none", paddingLeft: 22 }}
value={value[labelField]}
readOnly={true}
/>
<Icon
style={{ position: "absolute", top: -6, right: -4 }}
type="caret-down"
/>
</span>
</Dropdown>) : ""
)
</Dropdown>
) : (
""
);
}
}
export default DropdownSelect;

View File

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

View File

@ -1,16 +1,16 @@
// @ts-ignore
import React, { useState, useCallback } from 'react';
import { Modal, Form, Input, Tag } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import React, { useState, useCallback } from "react";
import { Modal, Form, Input, Tag } from "antd";
import { PlusOutlined } from "@ant-design/icons";
interface ITagGeneratorProps {
value?: Array<string>,
value?: Array<string>;
onChange?: (val: Array<string>) => void;
}
const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
export const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
const [inputVisible, setInputVisible] = useState<boolean>(false);
const [inputValue, setInputValue] = useState('');
const [inputValue, setInputValue] = useState("");
const handleInputChange = (e) => {
setInputValue(e.target.value);
@ -20,31 +20,52 @@ const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
setInputVisible(true);
};
const handleInputConfirm = useCallback((e) => {
const handleInputConfirm = useCallback(
(e) => {
onChange([...(value || []), e.target.value]);
setInputVisible(false);
setInputValue('');
}, [value]);
setInputValue("");
},
[value]
);
const handleRemove = useCallback((index) => {
const handleRemove = useCallback(
(index) => {
const newValue = [...value];
newValue.splice(index, 1);
onChange(newValue);
}, [value]);
},
[value]
);
return (
<div>
{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 && (
<Tag onClick={showInput} style={{ padding: '4px 6px', fontSize: 16 }}>
<Tag onClick={showInput} style={{ padding: "4px 6px", fontSize: 16 }}>
<PlusOutlined />
</Tag>
)}
</div>
)
);
};
interface ICommonCommandModalProps {
@ -60,21 +81,27 @@ const CommonCommandModal = Form.create()((props: ICommonCommandModalProps) => {
try {
const values = await form.validateFields();
props.onConfirm(values);
} catch (e) {
}
} catch (e) {}
};
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.Item label="标题">
{form.getFieldDecorator('title', {
rules: [{ required: true, message: '请输入标题' }]
{form.getFieldDecorator("title", {
rules: [{ required: true, message: "请输入标题" }],
})(<Input />)}
</Form.Item>
<Form.Item label="标签">
{form.getFieldDecorator('tag')( <TagGenerator />)}
{form.getFieldDecorator("tag")(<TagGenerator />)}
</Form.Item>
</Form>
</Modal>

View File

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

View File

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

View File

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

View File

@ -16,11 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import { escapeRegExp } from 'lodash';
import { DocViewTableRow } from './table_row';
import { arrayContainsObjects, trimAngularSpan } from './table_helper';
import { DocViewRenderProps } from '../../doc_views/doc_views_types';
import React, { useState } from "react";
import { escapeRegExp } from "lodash";
import { DocViewTableRow } from "./table_row";
import { arrayContainsObjects, trimAngularSpan } from "./table_helper";
import { DocViewRenderProps } from "../../doc_views/doc_views_types";
const COLLAPSE_LINE_LENGTH = 350;
@ -34,8 +34,10 @@ export function DocViewTable({
}: DocViewRenderProps) {
const mapping = indexPattern.fields.getByName;
const flattened = indexPattern.flattenHit(hit);
const formatted = indexPattern.formatHit(hit, 'html');
const [fieldRowOpen, setFieldRowOpen] = useState({} as Record<string, boolean>);
const formatted = indexPattern.formatHit(hit, "html");
const [fieldRowOpen, setFieldRowOpen] = useState(
{} as Record<string, boolean>
);
function toggleValueCollapse(field: string) {
fieldRowOpen[field] = fieldRowOpen[field] !== true;
@ -64,8 +66,10 @@ export function DocViewTable({
}
: undefined;
const isArrayOfObjects =
Array.isArray(flattened[field]) && arrayContainsObjects(flattened[field]);
const displayUnderscoreWarning = !mapping(field) && field.indexOf('_') === 0;
Array.isArray(flattened[field]) &&
arrayContainsObjects(flattened[field]);
const displayUnderscoreWarning =
!mapping(field) && field.indexOf("_") === 0;
const displayNoMappingWarning =
!mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects;
@ -107,10 +111,16 @@ export function DocViewTable({
!indexPattern.fields.getByName(field) &&
!!indexPattern.fields.getAll().find((patternField) => {
// We only want to match a full path segment
const nestedRootRegex = new RegExp(escapeRegExp(field) + '(\\.|$)');
return nestedRootRegex.test(patternField.subType?.nested?.path ?? '');
const nestedRootRegex = new RegExp(
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 (
<DocViewTableRow
@ -122,7 +132,9 @@ export function DocViewTable({
displayNoMappingWarning={displayNoMappingWarning}
isCollapsed={isCollapsed}
isCollapsible={isCollapsible}
isColumnActive={Array.isArray(columns) && columns.includes(field)}
isColumnActive={
Array.isArray(columns) && columns.includes(field)
}
onFilter={filter}
onToggleCollapse={() => toggleValueCollapse(field)}
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 {connect} from 'dva';
import {formatMessage} from 'umi/locale';
import {Button, Card, Col, DatePicker, Dropdown, Icon, Input, InputNumber, Row, Select, Statistic} from 'antd';
import moment from 'moment';
import {DateTime} from 'luxon';
import React, { PureComponent, useState } from "react";
import { connect } from "dva";
import { formatMessage } from "umi/locale";
import {
Button,
Card,
Col,
DatePicker,
Dropdown,
Icon,
Input,
InputNumber,
Row,
Select,
Statistic,
} from "antd";
import moment from "moment";
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 {
Axis,
Chart,
@ -18,71 +27,34 @@ import {
Position,
ScaleType,
Settings,
timeFormatter
timeFormatter,
} from "@elastic/charts";
import styles from './Metrics.less';
import styles from "./Metrics.less";
import { Spin, Alert } from 'antd';
import {EuiSuperDatePicker} from '@elastic/eui';
import {calculateBounds} from '../../components/kibana/data/common/query/timefilter';
import { Spin, Alert, Tabs } from "antd";
import { EuiSuperDatePicker } from "@elastic/eui";
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;
let fetchDataCount = 0;
let tv1 = null;
let HealthCircle = (props) => {
return (
<div style={{
<div
style={{
background: props.color,
width: 12,
height: 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";
@ -112,40 +84,45 @@ const theme = {
},
axes: {
axisTitle: {
fill: '#333',
fill: "#333",
fontSize: 12,
fontStyle: 'bold',
fontStyle: "bold",
fontFamily: "'Open Sans', Helvetica, Arial, sans-serif",
padding: 2,
},
axisLine: {
stroke: '#333',
stroke: "#333",
},
tickLabel: {
fill: '#333',
fill: "#333",
fontSize: 10,
fontFamily: "'Open Sans', Helvetica, Arial, sans-serif",
fontStyle: 'normal',
fontStyle: "normal",
padding: 2,
},
tickLine: {
visible: true,
stroke: '#333',
stroke: "#333",
strokeWidth: 1,
padding: 0,
},
},
}
};
const vstyle = {
fontSize: 12,
wordBreak: "break-all",
marginRight: "5px"
marginRight: "5px",
};
const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading}) => {
const MonitorDatePicker = ({
timeRange,
commonlyUsedRanges,
onChange,
isLoading,
}) => {
// const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]);
const [isPaused, setIsPaused] = useState(true);
const [isPaused, setIsPaused] = useState(false);
const [refreshInterval, setRefreshInterval] = useState(10000);
const onTimeChange = ({ start, end }) => {
@ -156,7 +133,7 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
};
const onRefresh = ({ start, end, refreshInterval }) => {
onChange({start, end})
onChange({ start, end });
};
const onRefreshChange = ({ isPaused, refreshInterval }) => {
@ -166,7 +143,7 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
return (
<EuiSuperDatePicker
dateFormat= ''
dateFormat=""
isLoading={isLoading}
start={timeRange?.min}
end={timeRange?.max}
@ -184,40 +161,40 @@ const MonitorDatePicker = ({timeRange, commonlyUsedRanges, onChange, isLoading})
@connect(({ clusterMonitor, global }) => ({
clusterMonitor,
selectedCluster: global.selectedCluster,
clusterList: global.clusterList
clusterList: global.clusterList,
}))
class ClusterMonitor extends PureComponent {
constructor(props) {
super(props);
//this.timePicker = React.createRef();
this.handleChartBrush = this.handleChartBrush.bind(this);
}
state = {
this.chartRefs = [];
this.handleTimeChange = this.handleTimeChange.bind(this);
this.state = {
spinning: false,
clusterID: null,
timeRange: {
min: 'now-1h', //moment().subtract(1, 'h').toISOString(),
max: 'now'//moment().toISOString()
min: "now-1h", //moment().subtract(1, 'h').toISOString(),
max: "now", //moment().toISOString()
timeFormatter: formatter.dates(1),
},
};
}
componentWillReceiveProps(newProps) {
}
componentWillReceiveProps(newProps) {}
fetchData = () => {
console.log("fetching data ing."+this.state.clusterID)
if (this.state.clusterID===undefined||this.state.clusterID===""||this.state.clusterID===null){
return
if (
this.state.clusterID === undefined ||
this.state.clusterID === "" ||
this.state.clusterID === null
) {
return;
}
this.setState({
spinning: true,
})
});
fetchDataCount++;
const { dispatch } = this.props;
@ -227,32 +204,30 @@ class ClusterMonitor extends PureComponent {
to: timeRange.max,
});
dispatch({
type: 'clusterMonitor/fetchClusterMetrics',
type: "clusterMonitor/fetchClusterMetrics",
payload: {
timeRange: {
min: bounds.min.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({
spinning: false,
})
})
}
componentWillUnmount() {
clearInterval(tv1);
}
});
});
};
componentDidUpdate(prevProps, prevState, snapshot) {
// console.log(this.props.selectedCluster)
// console.log(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 }, () => {
//TODO 处理 cancel 事件,先把当前还在执行中的请求结束,避免更新完成之后,被晚到的之前的请求给覆盖了。
@ -261,41 +236,52 @@ class ClusterMonitor extends PureComponent {
}
}
pointerUpdate = (event) => {
this.chartRefs.forEach((ref) => {
if (ref.current) {
ref.current.dispatchExternalPointerEvent(event);
}
});
};
componentDidMount() {
const { match, location } = this.props;
const queryESID = this.props.match.params.cluster_id;
if (queryESID !== null && queryESID !== undefined) {
this.state.clusterID=queryESID
this.state.clusterID = queryESID;
const { dispatch } = this.props;
dispatch({
type: 'global/changeClusterById',
type: "global/changeClusterById",
payload: {
id: queryESID
}
})
}else if (this.props.selectedCluster.id!==undefined&&this.props.selectedCluster.id!==null){
this.setState({ clusterID:this.props.selectedCluster.id }, () => {
id: queryESID,
},
});
} else if (
this.props.selectedCluster.id !== undefined &&
this.props.selectedCluster.id !== null
) {
this.setState({ clusterID: this.props.selectedCluster.id }, () => {});
} else {
//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 max = location.query.end || this.state.timeRange.max; //'2020-12-10 16:00';
this.setState({
this.setState(
{
timeRange: {
...this.state.timeRange,
min: min,
max: max,
},
}, () => {
},
() => {
this.fetchData();
})
}
);
// tv1 = setInterval(()=>{
// this.fetchData();
@ -312,7 +298,6 @@ class ClusterMonitor extends PureComponent {
}, durationInSeconds * 1000);
}
handleChartBrush({ x }) {
if (!x) {
return;
@ -321,28 +306,44 @@ class ClusterMonitor extends PureComponent {
let timeRange = {
min: moment(from).toISOString(),
max: moment(to).toISOString(),
}
this.setState({
};
timeRange.day = moment
.duration(moment(to).valueOf() - moment(from).valueOf())
.asDays();
this.setState(
{
timeRange: timeRange,
}, () => {
},
() => {
this.fetchData();
});
}
);
}
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: {
min: start,
max: end,
}
},()=>{
timeFormatter: formatter.dates(intDay),
},
},
() => {
this.fetchData();
})
}
);
};
render() {
const { clusterMonitor } = this.props;
let clusterStats = {};
let clusterMetrics = {};
@ -371,57 +372,56 @@ class ClusterMonitor extends PureComponent {
clusterMetrics = clusterMonitor.metrics;
}
const commonlyUsedRanges =
[
const commonlyUsedRanges = [
{
from: 'now-15m',
to: 'now',
display: '最近15分钟'
from: "now-15m",
to: "now",
display: "最近15分钟",
},
{
from: 'now-30m',
to: 'now',
display: '最近30分钟'
from: "now-30m",
to: "now",
display: "最近30分钟",
},
{
from: 'now-1h',
to: 'now',
display: '最近一小时'
from: "now-1h",
to: "now",
display: "最近一小时",
},
{
from: 'now-24h',
to: 'now',
display: '最近一天',
from: "now-24h",
to: "now",
display: "最近一天",
},
{
from: 'now/d',
to: 'now/d',
display:'今天',
from: "now/d",
to: "now/d",
display: "今天",
},
{
from: 'now/w',
to: 'now/w',
display: '这个星期'
from: "now/w",
to: "now/w",
display: "这个星期",
},
{
from: 'now-7d',
to: 'now',
display: '最近一周',
from: "now-7d",
to: "now",
display: "最近一周",
},
{
from: 'now-30d',
to: 'now',
display: '最近一个月',
from: "now-30d",
to: "now",
display: "最近一个月",
},
{
from: 'now-90d',
to: 'now',
display: '最近三个月',
from: "now-90d",
to: "now",
display: "最近三个月",
},
{
from: 'now-1y',
to: 'now',
display: '最近一年',
from: "now-1y",
to: "now",
display: "最近一年",
},
].map(({ from, to, display }) => {
return {
@ -433,123 +433,235 @@ class ClusterMonitor extends PureComponent {
return (
<Spin spinning={this.state.spinning} tip="Loading...">
<div>
<div style={{ background: "#fff" }}>
<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}
onChange={this.handleTimeChange} />
onChange={this.handleTimeChange}
/>
</div>
<Card
//title={this.state.clusterID?this.state.clusterID:''}
style={{marginBottom: 5}}>
<Row>
<div
style={{
padding: 15,
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}>
<Statistic valueStyle={vstyle} title="集群名称" value={clusterStats.cluster_name}/>
<Statistic
valueStyle={vstyle}
title="集群名称"
value={clusterStats.cluster_name}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="在线时长" value={clusterStats.uptime}/>
<Statistic
valueStyle={vstyle}
title="在线时长"
value={clusterStats.uptime}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="集群版本" value={clusterStats.version}/>
<Statistic
valueStyle={vstyle}
title="集群版本"
value={clusterStats.version}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="健康情况" value={clusterStats.status}
prefix={<HealthCircle color={clusterStats.status}/>}/>
<Statistic
valueStyle={vstyle}
title="健康情况"
value={clusterStats.status}
prefix={<HealthCircle color={clusterStats.status} />}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="节点数" value={clusterStats.nodes_count}/>
<Statistic
valueStyle={vstyle}
title="节点数"
value={clusterStats.nodes_count}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="索引数" value={clusterStats.indices_count}/>
<Statistic
valueStyle={vstyle}
title="索引数"
value={clusterStats.indices_count}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="主/总分片"
value={clusterStats.primary_shards + '/' + clusterStats.total_shards}/>
<Statistic
valueStyle={vstyle}
title="主/总分片"
value={
clusterStats.primary_shards +
"/" +
clusterStats.total_shards
}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="未分配分片"
value={clusterStats.unassigned_shards}/>
<Statistic
valueStyle={vstyle}
title="未分配分片"
value={clusterStats.unassigned_shards}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="文档数" value={clusterStats.document_count}/>
<Statistic
valueStyle={vstyle}
title="文档数"
value={clusterStats.document_count}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="存储空间"
value={clusterStats.used_store_bytes + '/' + clusterStats.max_store_bytes}/>
<Statistic
valueStyle={vstyle}
title="存储空间"
value={
clusterStats.used_store_bytes +
"/" +
clusterStats.max_store_bytes
}
/>
</Col>
<Col md={2} xs={4}>
<Statistic valueStyle={vstyle} title="JVM 内存"
value={clusterStats.used_jvm_bytes + '/' + clusterStats.max_jvm_bytes}/>
<Statistic
valueStyle={vstyle}
title="JVM 内存"
value={
clusterStats.used_jvm_bytes +
"/" +
clusterStats.max_jvm_bytes
}
/>
</Col>
</Row>
</Card>
{
Object.keys(clusterMetrics).map((e, i) => {
let axis = clusterMetrics[e].axis
let lines = clusterMetrics[e].lines
let disableHeaderFormat = false
let headerUnit = ""
</div>
<div>
<Tabs animated={false}>
<Tabs.TabPane key="cluster" tab="Cluster">
{Object.keys(clusterMetrics).map((e, i) => {
let axis = clusterMetrics[e].axis;
let lines = clusterMetrics[e].lines;
let disableHeaderFormat = false;
let headerUnit = "";
return (
<div key={e} className={styles.vizChartContainer}>
<Chart size={[, 200]} className={styles.vizChartItem}>
<Settings theme={theme} showLegend legendPosition={Position.Top}
<Chart
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}
tooltip={{
headerFormatter: disableHeaderFormat
? undefined
: ({value}) => `${formatter.full_dates(value)}${headerUnit ? ` ${headerUnit}` : ''}`,
: ({ value }) =>
`${formatter.full_dates(value)}${
headerUnit ? ` ${headerUnit}` : ""
}`,
}}
debug={false}/>
<Axis id="{e}-bottom" position={Position.Bottom} showOverlappingTicks
labelFormat={formatter.dates}
tickFormat={formatter.dates}
debug={false}
/>
{
axis.map((item) => {
return <Axis key={e + '-' + item.id}
id={e + '-' + item.id}
<Axis
id="{e}-bottom"
position={Position.Bottom}
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}
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}
ticks={item.ticks}
labelFormat={getFormatter(item.formatType, item.labelFormat)}
tickFormat={getFormatter(item.formatType, item.tickFormat)}
labelFormat={getFormatter(
item.formatType,
item.labelFormat
)}
tickFormat={getFormatter(
item.formatType,
item.tickFormat
)}
/>
})
}
);
})}
{
lines.map((item) => {
return <LineSeries key={item.metric.label}
{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)}
tickFormat={getFormatter(
item.metric.formatType,
item.metric.tickFormat,
item.metric.units
)}
yAccessors={[1]}
data={item.data}
curve={CurveType.CURVE_MONOTONE_X}
/>
})
}
);
})}
</Chart>
</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>
</Spin>
);

View File

@ -1,6 +1,6 @@
.vizChartContainer {
padding: 0px;
border-bottom: 1px solid rgb(232, 232, 232);
}
.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";
export default {
namespace: 'clusterMonitor',
state: {
},
namespace: "clusterMonitor",
state: {},
effects: {
*fetchClusterOverview({ payload, callback }, { call, put }) {
let clusterData = yield call(getClusterOverview, payload);
yield put({type: 'saveData', payload: clusterData})
if(callback && typeof callback == 'function'){
yield put({ type: "saveData", payload: clusterData });
if (callback && typeof callback == "function") {
callback(clusterData);
}
},
*fetchClusterMetrics({ payload, callback }, { call, put }) {
let clusterMetrics = yield call(getClusterMetrics, payload);
yield put({type: 'saveData', payload: clusterMetrics})
if(callback && typeof callback == 'function'){
yield put({ type: "saveData", payload: clusterMetrics });
if (callback && typeof callback == "function") {
callback(clusterMetrics);
console.log("finished call:" + params.cluster_id);
}
return clusterMetrics;
},
*fetchClusterNodeStats({ callback }, { call, put }) {
let nodesStats = yield call(getClusterNodeStats);
//yield put({type: 'saveData', payload: nodesStats})
if(callback && typeof callback == 'function'){
if (callback && typeof callback == "function") {
callback(nodesStats);
}
},
*fetchClusterList({ callback }, { call, put }) {
let clusterData = yield call(getClusterList);
yield put({type: 'saveData', payload: {
clusterList: clusterData
}})
if(callback && typeof callback == 'function'){
yield put({
type: "saveData",
payload: {
clusterList: clusterData,
},
});
if (callback && typeof callback == "function") {
callback(clusterData);
}
}
},
},
reducers: {
saveData(state, { payload }) {
return {
...state,
...payload
...payload,
};
},
}
},
};

View File

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

View File

@ -10,7 +10,6 @@ import {
useEffect,
useMemo,
useRef,
useLayoutEffect,
} from "react";
import { useLocalStorage } from "@/lib/hooks/storage";
import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings";
@ -195,12 +194,16 @@ export const ConsoleUI = ({
const [tabState, dispatch] = useReducer(consoleTabReducer, localState);
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();
return;
}
setLocalState(tabState);
}, [tabState]);
}, [tabState, clusterMap]);
const saveEditorContent = useCallback(
(content) => {

View File

@ -1,18 +1,18 @@
import { Button, Dropdown, List, Spin, message, Icon, Input } from 'antd';
import * as React from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import styles from '@/components/GlobalHeader/DropdownSelect.less';
import { Button, Dropdown, List, Spin, message, Icon, Input } from "antd";
import * as React from "react";
import InfiniteScroll from "react-infinite-scroller";
import styles from "@/components/GlobalHeader/DropdownSelect.less";
import _ from "lodash";
import {DropdownItem} from '@/components/GlobalHeader/DropdownItem';
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
import { DropdownItem } from "@/components/GlobalHeader/DropdownItem";
import { HealthStatusCircle } from "@/components/infini/health_status_circle";
class NewTabMenu extends React.Component {
handleItemClick = (item) => {
const onItemClick = this.props.onItemClick;
if(onItemClick && typeof onItemClick == 'function'){
onItemClick(item)
}
if (onItemClick && typeof onItemClick == "function") {
onItemClick(item);
}
};
constructor(props) {
super(props);
this.state = {
@ -24,13 +24,21 @@ class NewTabMenu extends React.Component{
dataSourceKey: 1,
selectedIndex: -1,
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) => {
let { size } = this.props;
@ -38,20 +46,19 @@ class NewTabMenu extends React.Component{
let { hasMore, dataSource } = this.state;
if (dataSource.length < targetLength) {
targetLength = dataSource.length;
hasMore = false
hasMore = false;
}
const newData = this.state.dataSource.slice(0, targetLength);
this.setState({
data: newData,
hasMore: hasMore,
})
}
});
};
handleInputChange = (e) => {
const name = e.target.value;
const newData = this.props.data.filter(item=>{
const newData = this.props.data.filter((item) => {
return item.name.includes(name);
});
this.setState({
@ -60,9 +67,8 @@ class NewTabMenu extends React.Component{
data: newData,
hasMore: newData.length > this.props.size,
selectedIndex: -1,
})
}
});
};
selectOffset = (offset) => {
let { selectedIndex, data } = this.state;
@ -71,8 +77,8 @@ class NewTabMenu extends React.Component{
// const item = data[selectedIndex];
this.setState({
selectedIndex,
})
}
});
};
onKeyDown = (e) => {
const { which } = e;
@ -82,33 +88,49 @@ class NewTabMenu extends React.Component{
case 38:
this.selectOffset(-1);
e.preventDefault();
e.stopPropagation()
e.stopPropagation();
break;
case 40:
this.selectOffset(1);
e.stopPropagation()
e.stopPropagation();
break;
case 13:
const { data, selectedIndex } = this.state;
if (selectedIndex > -1) {
this.handleItemClick(data[selectedIndex]);
this.setState({ overlayVisible: false })
this.setState({ overlayVisible: false });
}
break;
}
}
};
render() {
const { clusterStatus } = this.props;
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
<div className={styles.infiniteContainer} style={{height: this.props.height}}
onMouseEnter={()=>{this.searchInputRef.focus()}}
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||''} />
const menu = (
<div className={styles.dropmenu} style={{ width: this.props.width }}>
<div
className={styles.infiniteContainer}
style={{ height: this.props.height }}
onMouseEnter={() => {
this.searchInputRef.focus();
}}
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>
<InfiniteScroll
initialLoad={this.state.initialLoad}
@ -117,40 +139,57 @@ class NewTabMenu extends React.Component{
useWindow={false}
>
<div className={styles.dslist}>
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
{(!this.state.data || !this.state.data.length) && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
匹配不到集群(匹配规则为前缀匹配)
</div>
)}
{(this.state.data || []).map((item, idx) => {
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return <DropdownItem key={item.id}
return (
<DropdownItem
key={item.id}
clusterItem={item}
clusterStatus={cstatus}
isSelected={this.state.selectedIndex == idx}
onClick={() => {
this.handleItemClick(item)
this.handleItemClick(item);
}}
/>
);
})}
</div>
</InfiniteScroll>
</div>
{!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
</div>
)}
</div>);
</div>
);
return (
<div>
<Dropdown overlay={menu} placement="bottomLeft"
<Dropdown
overlay={menu}
placement="bottomLeft"
visible={this.state.overlayVisible}
onVisibleChange={(flag) => {
this.setState({ overlayVisible: flag });
}}>
}}
>
{this.props.children}
</Dropdown>
</div>
)
);
}
}
export default NewTabMenu;

View File

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

View File

@ -1,6 +1,6 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import {Link} from 'umi';
import React, { PureComponent, Fragment } from "react";
import { connect } from "dva";
import { Link } from "umi";
import {
Row,
Col,
@ -17,105 +17,31 @@ import {
Menu,
Table,
Dropdown,
Icon, Popconfirm,
Icon,
Popconfirm,
Switch,
} from 'antd';
import Editor from '@monaco-editor/react';
} from "antd";
// 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 {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from "../../List/TableList.less";
import { transformSettingsForApi } from "@/lib/elasticsearch/edit_settings";
import PageHeaderWrapper from "@/components/PageHeaderWrapper";
import { TagGenerator } from "@/components/kibana/console/components/CommonCommandModal";
const FormItem = Form.Item;
const { TextArea } = Input;
const { TabPane } = Tabs;
class JSONWrapper extends PureComponent {
state ={
height: 400,
}
componentDidMount(){
let getElementTop = (elem)=>{
  var elemTop=elem.offsetTop;
  elem=elem.offsetParent;
  while(elem!=null){
    elemTop+=elem.offsetTop;
    elem=elem.offsetParent;
  }
  return elemTop;
}
// console.log(getElementTop(this.refs.jsonw));
this.setState({height: window.innerHeight - getElementTop(this.refs.jsonw) -50});
}
render(){
return (
<div id="jsonw" ref="jsonw" onClick={()=>{console.log(document.getElementById('jsonw').offsetTop)}} style={{overflow:"scroll", height: this.state.height}}> {this.props.children}</div>
)
}
}
@Form.create()
class CreateForm extends React.Component {
okHandle = () => {
const {handleAdd, form} = this.props;
const me = this;
form.validateFields((err, fieldsValue) => {
if (err) return;
fieldsValue['config'] = me.editor.getValue();
handleAdd(fieldsValue);
form.resetFields();
});
};
onEditorDidMount = (editor)=>{
this.editor = editor;
}
render() {
const {modalVisible, form, handleModalVisible} = this.props;
return (
<Modal
destroyOnClose
title="新建索引"
visible={modalVisible}
width={640}
onOk={this.okHandle}
onCancel={() => handleModalVisible()}
>
<FormItem labelCol={{span: 5}} wrapperCol={{span: 15}} label="索引名称">
{form.getFieldDecorator('index', {
rules: [{required: true, message: '请输入至少五个字符的名称!', min: 5}],
})(<Input placeholder="请输入名称"/>)}
</FormItem>
<FormItem labelCol={{span: 5}} wrapperCol={{span: 15}} label="索引设置">
<div style={{border: '1px solid rgb(232, 232, 232)'}}>
<Editor
height="300px"
language="json"
theme="light"
options={{
minimap: {
enabled: false,
},
tabSize: 2,
wordBasedSuggestions: true,
}}
onMount={this.onEditorDidMount}
/>
</div>
</FormItem>
</Modal>
);
}
}
/* eslint react/no-multi-comp:0 */
@connect(({ command }) => ({
command
command,
}))
@Form.create()
class Index extends PureComponent {
@ -126,34 +52,41 @@ class Index extends PureComponent {
formValues: {},
drawerVisible: false,
editingCommand: {},
indexActiveKey: '1',
indexActiveKey: "1",
showSystemIndices: false,
};
columns = [
{
title: '名称',
dataIndex: 'title',
title: "名称",
dataIndex: "title",
render: (text, record) => (
<a onClick={()=>{
<a
onClick={() => {
this.setState({
editingCommand: record,
drawerVisible: true,
});
}}>{text}</a>
)
}}
>
{text}
</a>
),
},
{
title: '标签',
dataIndex: 'tag',
// render: (val)=>{
// return val || 0;
// }
title: "标签",
dataIndex: "tag",
render: (val) => {
return (val || []).join(",");
},
},
{
title: '操作',
title: "操作",
render: (text, record) => (
<Fragment>
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.id)}>
<Popconfirm
title="Sure to delete?"
onConfirm={() => this.handleDeleteClick(record.id)}
>
<a>删除</a>
</Popconfirm>
</Fragment>
@ -162,18 +95,18 @@ class Index extends PureComponent {
];
componentDidMount() {
this.fetchData()
this.fetchData();
}
fetchData = (params = {}) => {
const { dispatch } = this.props;
dispatch({
type: 'command/fetchCommandList',
type: "command/fetchCommandList",
payload: {
...params
}
...params,
},
});
}
};
handleFormReset = () => {
const { form, dispatch } = this.props;
@ -186,65 +119,109 @@ class Index extends PureComponent {
handleDeleteClick = (id) => {
const { dispatch } = this.props;
dispatch({
type: 'command/removeCommand',
type: "command/removeCommand",
payload: {
id: id,
},
}).then((res) => {
if (!res.error) {
message.success("删除成功!");
}
});
};
handleSearch = e => {
handleSearch = (e) => {
e.preventDefault();
const { dispatch, form } = this.props;
form.validateFields((err, fieldsValue) => {
if (err) return;
this.fetchData({
title: fieldsValue.name,
keyword: fieldsValue.keyword,
from: 0,
size: 10,
})
});
this.setState({
searchKey: fieldsValue.name,
searchKey: fieldsValue.keyword,
});
});
};
handleModalVisible = flag => {
handleModalVisible = (flag) => {
this.setState({
modalVisible: !!flag,
});
};
handleIndexTabChanged = (activeKey, indexName) => {
}
handleIndexTabChanged = (activeKey, indexName) => {};
handleEditorDidMount = (editorName, editor) => {
this[editorName] = editor;
}
};
handleIndexSettingsSaveClick = (indexName)=>{
}
buildRawCommonCommandRequest(cmd) {
const { requests } = cmd;
if (!requests) {
return '';
return "";
}
const strReqs = requests.map((req) => {
const { method, path, body } = req;
return `${method} ${path}\n${body}`;
})
return strReqs.join('\n');
});
return strReqs.join("\n");
}
handleRereshClick = () => {
const { searchKey } = this.state;
this.fetchData({
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() {
const { data, total } = this.props.command;
const { modalVisible, updateModalVisible, updateFormValues, drawerVisible, editingCommand } = this.state;
const {
modalVisible,
updateModalVisible,
updateFormValues,
drawerVisible,
editingCommand,
} = this.state;
const parentMethods = {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
@ -253,7 +230,9 @@ class Index extends PureComponent {
handleUpdateModalVisible: this.handleUpdateModalVisible,
handleUpdate: this.handleUpdate,
};
const {form: { getFieldDecorator }} = this.props;
const {
form: { getFieldDecorator },
} = this.props;
return (
<PageHeaderWrapper>
@ -263,8 +242,10 @@ class Index extends PureComponent {
<Form onSubmit={this.handleSearch} layout="inline">
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}>
<FormItem label="名称">
{getFieldDecorator('name')(<Input placeholder="请输入" />)}
<FormItem label="关键词">
{getFieldDecorator("keyword")(
<Input placeholder="请输入" />
)}
</FormItem>
</Col>
<Col md={8} sm={24}>
@ -272,41 +253,78 @@ class Index extends PureComponent {
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
<Button
style={{ marginLeft: 8 }}
onClick={this.handleFormReset}
>
重置
</Button>
</span>
</Col>
<Col md={8} sm={24} style={{textAlign:"right"}}>
<Button icon="redo" style={{marginRight:10}} onClick={this.handleRereshClick}>刷新</Button>
</Col>
{/* <Col md={8} sm={24} style={{ textAlign: "right" }}>
<Button
icon="redo"
style={{ marginRight: 10 }}
onClick={this.handleRereshClick}
>
刷新
</Button>
</Col> */}
</Row>
</Form>
</div>
<Table bordered
<Table
bordered
dataSource={data}
rowKey='id'
pagination={
{pageSize: 10,}
}
rowKey="id"
pagination={{ pageSize: 10 }}
columns={this.columns}
/>
</div>
</Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} />
<Drawer title={editingCommand.title}
<Drawer
// title={editingCommand.title}
title="常用命令"
visible={drawerVisible}
onClose={() => {
this.setState({
drawerVisible: false,
indexActiveKey: '1',
indexActiveKey: "1",
});
}}
width={720}
>
<div style={{border: '1px solid rgb(232, 232, 232)'}}>
<Editor
<div
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"
language="text"
theme="light"
@ -319,8 +337,15 @@ class Index extends PureComponent {
tabSize: 2,
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>
</Drawer>
</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 {formatESSearchResult} from '@/lib/elasticsearch/util';
import { formatESSearchResult } from "@/lib/elasticsearch/util";
export default {
namespace: 'command',
state: {
},
namespace: "command",
state: {},
effects: {
*fetchCommandList({ payload }, { call, put, select }) {
let res = yield call(searchCommand, payload);
if (res.error) {
message.error(res.error)
message.error(res.error);
return false;
}
res = formatESSearchResult(res)
res = formatESSearchResult(res);
yield put({
type: 'saveData',
payload: res
})
type: "saveData",
payload: res,
});
},
*removeCommand({ payload }, { call, put, select }) {
let res = yield call(deleteCommand, payload)
let res = yield call(deleteCommand, payload);
if (res.error) {
message.error(res.error)
message.error(res.error);
return false;
}
let {data, total} = yield select(state => state.command);
let { data, total } = yield select((state) => state.command);
data = data.filter((item) => {
return item.id !== payload.id;
})
});
yield put({
type: 'saveData',
type: "saveData",
payload: {
data,
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;
},
},
@ -48,7 +57,7 @@ export default {
return {
...state,
...payload,
}
}
}
}
};
},
},
};

View File

@ -1,10 +1,10 @@
import request from '@/utils/request';
import {buildQueryArgs, pathPrefix} from './common';
import request from "@/utils/request";
import { buildQueryArgs, pathPrefix } from "./common";
export async function searchCommand(params) {
let url = `${pathPrefix}/elasticsearch/command`;
let args = buildQueryArgs({
title: params.title,
keyword: params.keyword,
from: params.from,
size: params.size,
});
@ -12,13 +12,21 @@ export async function searchCommand(params) {
url += args;
}
return request(url, {
method: 'GET'
method: "GET",
});
}
export async function deleteCommand(params) {
const url = `${pathPrefix}/elasticsearch/command/${params.id}`;
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,
});
}