monitor of node level and index level, common command manage
This commit is contained in:
parent
d65a7f55f8
commit
408c2feb50
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
1
main.go
1
main.go
|
@ -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()
|
||||
|
|
|
@ -1,97 +1,93 @@
|
|||
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' },
|
||||
// {
|
||||
// path: '/cluster/overview/',
|
||||
// name: 'overview',
|
||||
// component: './Cluster/Overview',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },
|
||||
// { path: '/', redirect: '/platform/gateway' },
|
||||
// {
|
||||
// path: '/cluster/overview/',
|
||||
// name: 'overview',
|
||||
// component: './Cluster/Overview',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/cluster/overview',
|
||||
name: 'overview',
|
||||
component: './Cluster/NewOverview',
|
||||
// hideInMenu: true,
|
||||
routes:[
|
||||
{ path: '/', redirect: '/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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',
|
||||
hideInMenu: true,
|
||||
},
|
||||
// {
|
||||
// path: '/cluster/logs/',
|
||||
// name: 'logging',
|
||||
// component: './Cluster/SearchMonitor',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },{
|
||||
// path: '/cluster/settings/',
|
||||
// name: 'settings',
|
||||
// component: './Cluster/Settings/Base',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/cluster/settings',
|
||||
// redirect: '/cluster/settings/repository',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/cluster/settings/repository',
|
||||
// component: './Cluster/Settings/Repository',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
]
|
||||
path: "/cluster/overview",
|
||||
name: "overview",
|
||||
component: "./Cluster/NewOverview",
|
||||
// hideInMenu: true,
|
||||
routes: [{ path: "/", redirect: "/" }],
|
||||
},
|
||||
{
|
||||
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",
|
||||
hideInMenu: true,
|
||||
},
|
||||
// {
|
||||
// path: '/cluster/logs/',
|
||||
// name: 'logging',
|
||||
// component: './Cluster/SearchMonitor',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },{
|
||||
// path: '/cluster/settings/',
|
||||
// name: 'settings',
|
||||
// component: './Cluster/Settings/Base',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/cluster/settings',
|
||||
// redirect: '/cluster/settings/repository',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/cluster/settings/repository',
|
||||
// component: './Cluster/Settings/Repository',
|
||||
// routes:[
|
||||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
],
|
||||
},
|
||||
//devtools
|
||||
// {
|
||||
|
@ -104,40 +100,38 @@ export default [
|
|||
// component: './DevTool/Console',
|
||||
// },
|
||||
|
||||
//alerting
|
||||
{
|
||||
path: '/alerting',
|
||||
name: 'alerting',
|
||||
icon: 'alert',
|
||||
routes: [{
|
||||
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'
|
||||
}]
|
||||
//alerting
|
||||
{
|
||||
path: "/alerting",
|
||||
name: "alerting",
|
||||
icon: "alert",
|
||||
routes: [
|
||||
{
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
//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: '/' },
|
||||
],
|
||||
path: '/data/views/',
|
||||
name: 'indexPatterns',
|
||||
component: './DataManagement/IndexPatterns',
|
||||
|
||||
routes: [{ path: "/", redirect: "/" }],
|
||||
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',
|
||||
routes: [
|
||||
{
|
||||
path: '/search/alias',
|
||||
redirect: '/search/alias/index',
|
||||
// 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",
|
||||
name: "alias",
|
||||
component: "./SearchManage/alias/Alias",
|
||||
routes: [
|
||||
{
|
||||
path: "/search/alias",
|
||||
redirect: "/search/alias/index",
|
||||
// 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/dict',
|
||||
|
@ -328,33 +310,33 @@ export default [
|
|||
// ]
|
||||
// }
|
||||
//, {
|
||||
// path: '/search/nlp',
|
||||
// name: 'nlp',
|
||||
// component: './SearchManage/nlp/NLP',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/search/nlp',
|
||||
// redirect: '/search/nlp/query',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/query',
|
||||
// component: './SearchManage/nlp/Query',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/intention',
|
||||
// component: './SearchManage/nlp/Intention',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/knowledge',
|
||||
// component: './SearchManage/nlp/Knowledge',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/text',
|
||||
// component: './SearchManage/nlp/Text',
|
||||
// }
|
||||
//]
|
||||
//},
|
||||
]
|
||||
// path: '/search/nlp',
|
||||
// name: 'nlp',
|
||||
// component: './SearchManage/nlp/NLP',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/search/nlp',
|
||||
// redirect: '/search/nlp/query',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/query',
|
||||
// component: './SearchManage/nlp/Query',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/intention',
|
||||
// component: './SearchManage/nlp/Intention',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/knowledge',
|
||||
// component: './SearchManage/nlp/Knowledge',
|
||||
// },
|
||||
// {
|
||||
// path: '/search/nlp/text',
|
||||
// component: './SearchManage/nlp/Text',
|
||||
// }
|
||||
//]
|
||||
//},
|
||||
],
|
||||
},
|
||||
//
|
||||
// //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",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,59 +1,71 @@
|
|||
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)
|
||||
this.state={
|
||||
class DropdownSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: props.defaultValue,
|
||||
loading: false,
|
||||
hasMore: props.data.length > props.size,
|
||||
overlayVisible: false,
|
||||
data: (props.data || []).slice(0, props.size),
|
||||
dataSource: [...props.data],
|
||||
};
|
||||
}
|
||||
|
||||
handleItemClick = (item) => {
|
||||
let preValue = this.props.value || this.state.value;
|
||||
this.setState(
|
||||
{
|
||||
value: item,
|
||||
overlayVisible: false,
|
||||
},
|
||||
() => {
|
||||
let onChange = this.props.onChange;
|
||||
if (preValue != item && onChange && typeof onChange == "function") {
|
||||
onChange(item);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount() {}
|
||||
componentDidUpdate(preProps, preState) {
|
||||
if (preProps.data.length != preState.dataSource.length) {
|
||||
const newData = [...preProps.data];
|
||||
this.setState({
|
||||
dataSource: newData,
|
||||
data: newData,
|
||||
hasMore: newData.length > this.props.size,
|
||||
selectedIndex: -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleItemClick = (item)=>{
|
||||
let preValue = this.props.value || this.state.value;
|
||||
this.setState({
|
||||
value: item,
|
||||
overlayVisible: false,
|
||||
},()=>{
|
||||
let onChange = this.props.onChange;
|
||||
if(preValue != item && onChange && typeof onChange == 'function'){
|
||||
onChange(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
}
|
||||
|
||||
handleInfiniteOnLoad = (current) => {
|
||||
let {size } = this.props;
|
||||
let { size } = this.props;
|
||||
let targetLength = current * size;
|
||||
let {hasMore, dataSource} = this.state;
|
||||
if(dataSource.length < targetLength){
|
||||
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) =>{
|
||||
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,76 +73,127 @@ class DropdownSelect extends React.Component{
|
|||
dataSource: newData,
|
||||
data: newData,
|
||||
hasMore: newData.length > this.props.size,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
render(){
|
||||
render() {
|
||||
let me = this;
|
||||
const {labelField, clusterStatus} = this.props;
|
||||
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||''} />
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
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}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!this.state.loading && this.state.hasMore}
|
||||
useWindow={false}
|
||||
>
|
||||
<div className={styles.dslist}>
|
||||
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
||||
{(this.state.data || []).map((item)=>{
|
||||
// return <div className={styles.item}>
|
||||
// <Button key={item[labelField]}
|
||||
// onClick={() => {
|
||||
// this.handleItemClick(item)
|
||||
// }}
|
||||
// className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
|
||||
// </div>
|
||||
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||
return <DropdownItem key={item.id}
|
||||
isSelected={item.id===value.id}
|
||||
clusterItem={item}
|
||||
clusterStatus={cstatus}
|
||||
onClick={() => {
|
||||
this.handleItemClick(item)
|
||||
}}
|
||||
/>
|
||||
})}
|
||||
>
|
||||
<div className={styles.dslist}>
|
||||
{(!this.state.data || !this.state.data.length) && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 50,
|
||||
}}
|
||||
>
|
||||
匹配不到集群(匹配规则为前缀匹配)
|
||||
</div>
|
||||
)}
|
||||
{(this.state.data || []).map((item) => {
|
||||
// return <div className={styles.item}>
|
||||
// <Button key={item[labelField]}
|
||||
// onClick={() => {
|
||||
// this.handleItemClick(item)
|
||||
// }}
|
||||
// className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
|
||||
// </div>
|
||||
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||
return (
|
||||
<DropdownItem
|
||||
key={item.id}
|
||||
isSelected={item.id === value.id}
|
||||
clusterItem={item}
|
||||
clusterStatus={cstatus}
|
||||
onClick={() => {
|
||||
this.handleItemClick(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
{!this.state.loading && this.state.hasMore && (
|
||||
<div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
|
||||
pull load more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!this.state.loading && this.state.hasMore && (
|
||||
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}>
|
||||
pull load more
|
||||
</div>
|
||||
)}
|
||||
</div>);
|
||||
);
|
||||
const cstatus = clusterStatus ? clusterStatus[value?.id] : null;
|
||||
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}}
|
||||
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'}} />}
|
||||
</i>
|
||||
<input className={styles['btn-ds']} style={{outline:'none', paddingLeft:22}} value={value[labelField]} readOnly={true} />
|
||||
<Icon style={{position:'absolute', top:-6, right:-4}} type="caret-down"/>
|
||||
</span>
|
||||
|
||||
</Dropdown>) : ""
|
||||
)
|
||||
<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"
|
||||
/>
|
||||
</span>
|
||||
</Dropdown>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DropdownSelect;
|
|
@ -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={()=>{
|
||||
const {history, selectedCluster} = this.props;
|
||||
// history.push(`/dev_tool`);
|
||||
this.setState({
|
||||
consoleVisible: !this.state.consoleVisible
|
||||
})
|
||||
}}> <Icon type="code" /></a>
|
||||
<a
|
||||
className={styles.action}
|
||||
onClick={() => {
|
||||
const { history, selectedCluster } = this.props;
|
||||
// history.push(`/dev_tool`);
|
||||
this.setState({
|
||||
consoleVisible: !this.state.consoleVisible,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<Icon type="code" />
|
||||
</a>
|
||||
|
||||
{/* <NoticeIcon
|
||||
className={styles.action}
|
||||
|
@ -158,17 +173,19 @@ 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',
|
||||
borderTop: "solid 1px #ddd",
|
||||
background: "#fff",
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 1002,
|
||||
}}>
|
||||
{/* <Resizable
|
||||
<div
|
||||
style={{
|
||||
display: this.state.consoleVisible ? "block" : "none",
|
||||
borderTop: "solid 1px #ddd",
|
||||
background: "#fff",
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 1002,
|
||||
}}
|
||||
>
|
||||
{/* <Resizable
|
||||
defaultSize={{
|
||||
height: '50vh'
|
||||
}}
|
||||
|
@ -185,19 +202,23 @@ export default class GlobalHeaderRight extends PureComponent {
|
|||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}> */}
|
||||
{this.props.clusterList.length > 0 && <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>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
onChange([...(value || []), e.target.value]);
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
}, [value]);
|
||||
const handleInputConfirm = useCallback(
|
||||
(e) => {
|
||||
onChange([...(value || []), e.target.value]);
|
||||
setInputVisible(false);
|
||||
setInputValue("");
|
||||
},
|
||||
[value]
|
||||
);
|
||||
|
||||
const handleRemove = useCallback((index) => {
|
||||
const newValue = [...value];
|
||||
newValue.splice(index, 1);
|
||||
onChange(newValue);
|
||||
}, [value]);
|
||||
const handleRemove = useCallback(
|
||||
(index) => {
|
||||
const newValue = [...value];
|
||||
newValue.splice(index, 1);
|
||||
onChange(newValue);
|
||||
},
|
||||
[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>
|
||||
|
|
|
@ -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 {
|
||||
|
@ -41,19 +47,16 @@ const SendRequestButton = (props: any) => {
|
|||
const sendCurrentRequestToES = useSendCurrentRequestToES();
|
||||
const saveCurrentTextObject = useSaveCurrentTextObject();
|
||||
|
||||
const {saveCurrentTextObjectRef} = props;
|
||||
useEffect(()=>{
|
||||
saveCurrentTextObjectRef.current = saveCurrentTextObject
|
||||
}, [saveCurrentTextObjectRef])
|
||||
|
||||
const { saveCurrentTextObjectRef } = props;
|
||||
useEffect(() => {
|
||||
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);
|
||||
|
@ -88,36 +96,44 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
|
|||
const consoleMenuRef = useRef<ConsoleMenu | null>(null);
|
||||
const aceEditorRef = useRef<ace.Editor | null>(null);
|
||||
|
||||
const sendCurrentRequestToESRef = useRef(()=>{});
|
||||
const saveCurrentTextObjectRef = useRef((content:string)=>{});
|
||||
const sendCurrentRequestToESRef = useRef(() => {});
|
||||
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',
|
||||
bindKey: {win: "Ctrl-enter", mac: "Command-enter|Ctrl-enter"},
|
||||
exec: ()=>{
|
||||
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,68 +163,76 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
|
|||
if (editorInstanceRef.current) {
|
||||
editorInstanceRef.current.getCoreEditor().destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
retrieveAutoCompleteInfo(settings, settings.getAutocomplete(), clusterID);
|
||||
aceEditorRef.current && (aceEditorRef.current['clusterID'] = clusterID);
|
||||
},[clusterID])
|
||||
aceEditorRef.current && (aceEditorRef.current["clusterID"] = clusterID);
|
||||
}, [clusterID]);
|
||||
|
||||
const handleSaveAsCommonCommand = async () => {
|
||||
const handleSaveAsCommonCommand = async () => {
|
||||
const editor = editorInstanceRef.current;
|
||||
if(editor == null){
|
||||
console.warn('editor is null')
|
||||
return
|
||||
if (editor == null) {
|
||||
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 className="conApp__editor">
|
||||
<ul className="conApp__autoComplete" id="autocomplete" />
|
||||
<EuiFlexGroup
|
||||
className="conApp__editorActions"
|
||||
id="ConAppEditorActions"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
ref={editorActionsRef}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SendRequestButton saveCurrentTextObjectRef={saveCurrentTextObjectRef}/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ConsoleMenu
|
||||
getCurl={() => {
|
||||
return editorInstanceRef.current!.getRequestsAsCURL('');
|
||||
}}
|
||||
getDocumentation={() => {
|
||||
return getDocumentation(editorInstanceRef.current!, '');
|
||||
}}
|
||||
autoIndent={(event) => {
|
||||
autoIndent(editorInstanceRef.current!, event);
|
||||
}}
|
||||
saveAsCommonCommand={handleSaveAsCommonCommand}
|
||||
ref={consoleMenuRef}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div
|
||||
ref={editorRef}
|
||||
id={`Editor_${editorInstanceRef.current?.paneKey}`}
|
||||
className="conApp__editorContent"
|
||||
data-test-subj="request-editor"
|
||||
onClick={()=>{consoleMenuRef.current?.closePopover(); aceEditorRef.current?.focus()}}
|
||||
<div
|
||||
style={{ ...abs, height: height }}
|
||||
data-test-subj="console-application"
|
||||
className="conApp"
|
||||
>
|
||||
<div className="conApp__editor">
|
||||
<ul className="conApp__autoComplete" id="autocomplete" />
|
||||
<EuiFlexGroup
|
||||
className="conApp__editorActions"
|
||||
id="ConAppEditorActions"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
ref={editorActionsRef}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SendRequestButton
|
||||
saveCurrentTextObjectRef={saveCurrentTextObjectRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ConsoleMenu
|
||||
getCurl={() => {
|
||||
return editorInstanceRef.current!.getRequestsAsCURL("");
|
||||
}}
|
||||
getDocumentation={() => {
|
||||
return getDocumentation(editorInstanceRef.current!, "");
|
||||
}}
|
||||
autoIndent={(event) => {
|
||||
autoIndent(editorInstanceRef.current!, event);
|
||||
}}
|
||||
saveAsCommonCommand={handleSaveAsCommonCommand}
|
||||
ref={consoleMenuRef}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div
|
||||
ref={editorRef}
|
||||
id={`Editor_${editorInstanceRef.current?.paneKey}`}
|
||||
className="conApp__editorContent"
|
||||
data-test-subj="request-editor"
|
||||
onClick={() => {
|
||||
consoleMenuRef.current?.closePopover();
|
||||
aceEditorRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -217,6 +244,3 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, hei
|
|||
|
||||
// export default ConsoleInput;
|
||||
export default ConsoleInputUI;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@import '../../../../../core/public/variables.scss';
|
||||
@import "../../../../../core/public/variables.scss";
|
||||
.kbnDocViewerTable {
|
||||
margin-top: $euiSizeS;
|
||||
font-size: 12px;
|
||||
.kbnDocViewer__buttons{
|
||||
.kbnDocViewer__buttons {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
|||
}
|
||||
.kbnDocViewer__field {
|
||||
padding-top: 8px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.dscFieldName {
|
||||
|
@ -73,4 +74,3 @@
|
|||
.kbnDocViewer__warning {
|
||||
margin-right: $euiSizeS;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 };
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import useAsync from "./use_async";
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
};
|
||||
|
||||
export default function useFetch(url, options = {}, dependencies = []) {
|
||||
return useAsync(() => {
|
||||
if (options.queryParams) {
|
||||
url +=
|
||||
"?" +
|
||||
Object.entries(options.queryParams)
|
||||
.map((kvs) => kvs.join("="))
|
||||
.join("&");
|
||||
}
|
||||
return fetch(url, { ...DEFAULT_OPTIONS, ...options }).then((res) => {
|
||||
if (res.ok) return res.json();
|
||||
return res.json().then((json) => Promise.reject(json));
|
||||
});
|
||||
}, dependencies);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +1,14 @@
|
|||
|
||||
.vizChartContainer{
|
||||
.vizChartContainer {
|
||||
padding: 0px;
|
||||
border-bottom: 1px solid rgb(232, 232, 232);
|
||||
}
|
||||
|
||||
.vizChartItem{
|
||||
background: white!important;
|
||||
.vizChartItem {
|
||||
background: white !important;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.vizChartTitle{
|
||||
.vizChartTitle {
|
||||
color: red;
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
.metric-control {
|
||||
display: flex;
|
||||
.selector {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
.value-selector {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.px {
|
||||
padding: 0 15px;
|
||||
}
|
|
@ -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}` : "");
|
||||
}
|
|
@ -1,50 +1,56 @@
|
|||
import {getClusterOverview, getClusterNodeStats, getClusterList} from "@/services/dashboard";
|
||||
import {getClusterMetrics} from "@/services/cluster";
|
||||
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") {
|
||||
callback(clusterData);
|
||||
}
|
||||
},
|
||||
effects:{
|
||||
*fetchClusterOverview({payload, callback}, {call, put}){
|
||||
let clusterData = yield call(getClusterOverview, payload);
|
||||
yield put({type: 'saveData', payload: clusterData})
|
||||
if(callback && typeof callback == 'function'){
|
||||
callback(clusterData);
|
||||
}
|
||||
},
|
||||
*fetchClusterMetrics({payload, callback}, {call, put}){
|
||||
let clusterMetrics = yield call(getClusterMetrics, payload);
|
||||
yield put({type: 'saveData', payload: clusterMetrics})
|
||||
if(callback && typeof callback == 'function'){
|
||||
callback(clusterMetrics);
|
||||
console.log("finished call:"+params.cluster_id);
|
||||
}
|
||||
},
|
||||
*fetchClusterNodeStats({callback}, {call, put}){
|
||||
let nodesStats = yield call(getClusterNodeStats);
|
||||
//yield put({type: 'saveData', payload: nodesStats})
|
||||
if(callback && typeof callback == 'function'){
|
||||
callback(nodesStats);
|
||||
}
|
||||
},
|
||||
*fetchClusterList({callback}, {call, put}){
|
||||
let clusterData = yield call(getClusterList);
|
||||
yield put({type: 'saveData', payload: {
|
||||
clusterList: clusterData
|
||||
}})
|
||||
if(callback && typeof callback == 'function'){
|
||||
callback(clusterData);
|
||||
}
|
||||
}
|
||||
*fetchClusterMetrics({ payload, callback }, { call, put }) {
|
||||
let clusterMetrics = yield call(getClusterMetrics, payload);
|
||||
yield put({ type: "saveData", payload: clusterMetrics });
|
||||
if (callback && typeof callback == "function") {
|
||||
callback(clusterMetrics);
|
||||
console.log("finished call:" + params.cluster_id);
|
||||
}
|
||||
return clusterMetrics;
|
||||
},
|
||||
reducers:{
|
||||
saveData(state, {payload}){
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
*fetchClusterNodeStats({ callback }, { call, put }) {
|
||||
let nodesStats = yield call(getClusterNodeStats);
|
||||
//yield put({type: 'saveData', payload: nodesStats})
|
||||
if (callback && typeof callback == "function") {
|
||||
callback(nodesStats);
|
||||
}
|
||||
},
|
||||
*fetchClusterList({ callback }, { call, put }) {
|
||||
let clusterData = yield call(getClusterList);
|
||||
yield put({
|
||||
type: "saveData",
|
||||
payload: {
|
||||
clusterList: clusterData,
|
||||
},
|
||||
}
|
||||
});
|
||||
if (callback && typeof callback == "function") {
|
||||
callback(clusterData);
|
||||
}
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
saveData(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
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)=>{
|
||||
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){
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
this.state = {
|
||||
loading: false,
|
||||
hasMore: (props.data || []).length > props.size,
|
||||
data: (props.data || []).slice(0, props.size || 10),
|
||||
|
@ -24,34 +24,41 @@ class NewTabMenu extends React.Component{
|
|||
dataSourceKey: 1,
|
||||
selectedIndex: -1,
|
||||
overlayVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
componentDidUpdate(preProps, preState) {
|
||||
if (preProps.data.length != preState.dataSource.length) {
|
||||
const newData = [...preProps.data];
|
||||
this.setState({
|
||||
dataSource: newData,
|
||||
data: newData,
|
||||
hasMore: newData.length > this.props.size,
|
||||
selectedIndex: -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
handleInfiniteOnLoad = (current) => {
|
||||
let {size } = this.props;
|
||||
let { size } = this.props;
|
||||
let targetLength = current * size;
|
||||
let {hasMore, dataSource} = this.state;
|
||||
if(dataSource.length < targetLength){
|
||||
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) =>{
|
||||
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,19 +67,18 @@ class NewTabMenu extends React.Component{
|
|||
data: newData,
|
||||
hasMore: newData.length > this.props.size,
|
||||
selectedIndex: -1,
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
selectOffset = (offset)=> {
|
||||
let {selectedIndex, data} = this.state;
|
||||
selectOffset = (offset) => {
|
||||
let { selectedIndex, data } = this.state;
|
||||
const len = data.length;
|
||||
selectedIndex = (selectedIndex + offset + len) % len;
|
||||
// const item = data[selectedIndex];
|
||||
this.setState({
|
||||
selectedIndex,
|
||||
})
|
||||
}
|
||||
// const item = data[selectedIndex];
|
||||
this.setState({
|
||||
selectedIndex,
|
||||
});
|
||||
};
|
||||
|
||||
onKeyDown = (e) => {
|
||||
const { which } = e;
|
||||
|
@ -82,75 +88,108 @@ 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){
|
||||
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||''} />
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
initialLoad={this.state.initialLoad}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!this.state.loading && this.state.hasMore}
|
||||
useWindow={false}
|
||||
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.dslist}>
|
||||
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
||||
{(this.state.data || []).map((item, idx)=>{
|
||||
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||
return <DropdownItem key={item.id}
|
||||
clusterItem={item}
|
||||
clusterStatus={cstatus}
|
||||
isSelected={this.state.selectedIndex == idx}
|
||||
onClick={() => {
|
||||
this.handleItemClick(item)
|
||||
<div
|
||||
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}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!this.state.loading && this.state.hasMore}
|
||||
useWindow={false}
|
||||
>
|
||||
<div className={styles.dslist}>
|
||||
{(!this.state.data || !this.state.data.length) && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 50,
|
||||
}}
|
||||
>
|
||||
匹配不到集群(匹配规则为前缀匹配)
|
||||
</div>
|
||||
)}
|
||||
{(this.state.data || []).map((item, idx) => {
|
||||
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||
return (
|
||||
<DropdownItem
|
||||
key={item.id}
|
||||
clusterItem={item}
|
||||
clusterStatus={cstatus}
|
||||
isSelected={this.state.selectedIndex == idx}
|
||||
onClick={() => {
|
||||
this.handleItemClick(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
{!this.state.loading && this.state.hasMore && (
|
||||
<div style={{ textAlign: "center", marginTop: 10, color: "#ccc" }}>
|
||||
pull load more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!this.state.loading && this.state.hasMore && (
|
||||
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}>
|
||||
pull load more
|
||||
</div>
|
||||
)}
|
||||
</div>);
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Dropdown overlay={menu} placement="bottomLeft"
|
||||
visible={this.state.overlayVisible}
|
||||
onVisibleChange={(flag)=>{
|
||||
this.setState({ overlayVisible: flag });
|
||||
}}>
|
||||
{this.props.children}
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
<Dropdown
|
||||
overlay={menu}
|
||||
placement="bottomLeft"
|
||||
visible={this.state.overlayVisible}
|
||||
onVisibleChange={(flag) => {
|
||||
this.setState({ overlayVisible: flag });
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NewTabMenu;
|
|
@ -1,166 +1,171 @@
|
|||
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig,
|
||||
tryConnect} from "@/services/cluster";
|
||||
import {message} from "antd";
|
||||
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
||||
import {
|
||||
createClusterConfig,
|
||||
searchClusterConfig,
|
||||
updateClusterConfig,
|
||||
deleteClusterConfig,
|
||||
tryConnect,
|
||||
} from "@/services/cluster";
|
||||
import { message } from "antd";
|
||||
import { formatESSearchResult } from "@/lib/elasticsearch/util";
|
||||
|
||||
export default {
|
||||
namespace: 'clusterConfig',
|
||||
namespace: "clusterConfig",
|
||||
state: {
|
||||
editMode: '',
|
||||
editMode: "",
|
||||
editValue: {},
|
||||
},
|
||||
effects:{
|
||||
*fetchClusterList({payload}, {call, put, select}){
|
||||
effects: {
|
||||
*fetchClusterList({ payload }, { call, put, select }) {
|
||||
let res = yield call(searchClusterConfig, payload);
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
if (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)
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
*addCluster({ payload }, { call, put, select }) {
|
||||
let res = yield call(createClusterConfig, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
let {data, total} = yield select(state => state.clusterConfig);
|
||||
if(!data){
|
||||
return
|
||||
let { data, total } = yield select((state) => state.clusterConfig);
|
||||
if (!data) {
|
||||
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)
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
*updateCluster({ payload }, { call, put, select }) {
|
||||
let res = yield call(updateClusterConfig, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
let {data} = yield select(state => state.clusterConfig);
|
||||
let idx = data.findIndex((item)=>{
|
||||
let { data } = yield select((state) => state.clusterConfig);
|
||||
let idx = data.findIndex((item) => {
|
||||
return item.id === res._id;
|
||||
});
|
||||
|
||||
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) {
|
||||
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{
|
||||
},
|
||||
});
|
||||
} else {
|
||||
yield put({
|
||||
type: 'global/removeCluster',
|
||||
type: "global/removeCluster",
|
||||
payload: {
|
||||
id: res._id,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
}else{
|
||||
} 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)
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
*deleteCluster({ payload }, { call, put, select }) {
|
||||
let res = yield call(deleteClusterConfig, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
let {data, total} = yield select(state => state.clusterConfig);
|
||||
data = data.filter((item)=>{
|
||||
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)
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
*doTryConnect({ payload }, { call, put, select }) {
|
||||
let res = yield call(tryConnect, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
type: "saveData",
|
||||
payload: {
|
||||
tempClusterInfo: res,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
return res;
|
||||
}
|
||||
},
|
||||
},
|
||||
reducers:{
|
||||
saveData(state, {payload}){
|
||||
reducers: {
|
||||
saveData(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
/* eslint react/no-multi-comp:0 */
|
||||
@connect(({ command }) => ({
|
||||
command
|
||||
command,
|
||||
}))
|
||||
@Form.create()
|
||||
class Index extends PureComponent {
|
||||
|
@ -125,35 +51,42 @@ class Index extends PureComponent {
|
|||
expandForm: false,
|
||||
formValues: {},
|
||||
drawerVisible: false,
|
||||
editingCommand:{},
|
||||
indexActiveKey: '1',
|
||||
editingCommand: {},
|
||||
indexActiveKey: "1",
|
||||
showSystemIndices: false,
|
||||
};
|
||||
columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'title',
|
||||
title: "名称",
|
||||
dataIndex: "title",
|
||||
render: (text, record) => (
|
||||
<a onClick={()=>{
|
||||
this.setState({
|
||||
editingCommand: record,
|
||||
drawerVisible: true,
|
||||
});
|
||||
}}>{text}</a>
|
||||
)
|
||||
<a
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
editingCommand: record,
|
||||
drawerVisible: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{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={})=>{
|
||||
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) => {
|
||||
}
|
||||
handleEditorDidMount = (editorName, editor)=>{
|
||||
handleIndexTabChanged = (activeKey, indexName) => {};
|
||||
handleEditorDidMount = (editorName, editor) => {
|
||||
this[editorName] = editor;
|
||||
}
|
||||
};
|
||||
|
||||
handleIndexSettingsSaveClick = (indexName)=>{
|
||||
}
|
||||
buildRawCommonCommandRequest(cmd){
|
||||
const {requests} = cmd;
|
||||
if(!requests){
|
||||
return '';
|
||||
buildRawCommonCommandRequest(cmd) {
|
||||
const { requests } = cmd;
|
||||
if (!requests) {
|
||||
return "";
|
||||
}
|
||||
const strReqs = requests.map((req)=>{
|
||||
const {method, path, body} = req;
|
||||
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;
|
||||
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 { data, total } = this.props.command;
|
||||
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,55 +253,99 @@ 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={()=>{
|
||||
onClose={() => {
|
||||
this.setState({
|
||||
drawerVisible: false,
|
||||
indexActiveKey: '1',
|
||||
indexActiveKey: "1",
|
||||
});
|
||||
}}
|
||||
width={720}
|
||||
>
|
||||
<div style={{border: '1px solid rgb(232, 232, 232)'}}>
|
||||
<Editor
|
||||
height="300px"
|
||||
language="text"
|
||||
theme="light"
|
||||
value={this.buildRawCommonCommandRequest(editingCommand)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
tabSize: 2,
|
||||
wordBasedSuggestions: true,
|
||||
}}
|
||||
onMount={(editor)=>this.handleEditorDidMount('indexSettingsEditor', editor)}
|
||||
/>
|
||||
<div
|
||||
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"
|
||||
value={this.buildRawCommonCommandRequest(editingCommand)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
tabSize: 2,
|
||||
wordBasedSuggestions: true,
|
||||
}}
|
||||
onMount={(editor) =>
|
||||
this.handleEditorDidMount("commandEditor", editor)
|
||||
}
|
||||
/> */}
|
||||
</div>
|
||||
<div style={{ marginTop: 15, textAlign: "right" }}>
|
||||
<Button type="primary" onClick={this.handleSaveClick}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer>
|
||||
</PageHeaderWrapper>
|
||||
|
|
|
@ -1,54 +1,63 @@
|
|||
import {searchCommand, deleteCommand} from "@/services/command";
|
||||
import {message} from "antd";
|
||||
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
||||
import { searchCommand, deleteCommand, saveCommand } from "@/services/command";
|
||||
import { message } from "antd";
|
||||
import { formatESSearchResult } from "@/lib/elasticsearch/util";
|
||||
|
||||
export default {
|
||||
namespace: 'command',
|
||||
state: {
|
||||
},
|
||||
effects:{
|
||||
*fetchCommandList({payload}, {call, put, select}){
|
||||
let res = yield call(searchCommand, payload);
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
return false;
|
||||
}
|
||||
res = formatESSearchResult(res)
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: res
|
||||
})
|
||||
},
|
||||
namespace: "command",
|
||||
state: {},
|
||||
effects: {
|
||||
*fetchCommandList({ payload }, { call, put, select }) {
|
||||
let res = yield call(searchCommand, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
res = formatESSearchResult(res);
|
||||
yield put({
|
||||
type: "saveData",
|
||||
payload: res,
|
||||
});
|
||||
},
|
||||
|
||||
*removeCommand({payload}, {call, put, select}) {
|
||||
let res = yield call(deleteCommand, payload)
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
return false;
|
||||
}
|
||||
let {data, total} = yield select(state => state.command);
|
||||
data = data.filter((item)=>{
|
||||
return item.id !== payload.id;
|
||||
})
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
data,
|
||||
total: {
|
||||
...total,
|
||||
value: total.value - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
return res;
|
||||
},
|
||||
},
|
||||
reducers:{
|
||||
saveData(state, {payload}){
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*removeCommand({ payload }, { call, put, select }) {
|
||||
let res = yield call(deleteCommand, payload);
|
||||
if (res.error) {
|
||||
message.error(res.error);
|
||||
return false;
|
||||
}
|
||||
let { data, total } = yield select((state) => state.command);
|
||||
data = data.filter((item) => {
|
||||
return item.id !== payload.id;
|
||||
});
|
||||
yield put({
|
||||
type: "saveData",
|
||||
payload: {
|
||||
data,
|
||||
total: {
|
||||
...total,
|
||||
value: total.value - 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
return res;
|
||||
},
|
||||
|
||||
*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;
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
saveData(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
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,
|
||||
from: params.from,
|
||||
size: params.size,
|
||||
});
|
||||
if(args.length > 0){
|
||||
url += args;
|
||||
}
|
||||
return request(url, {
|
||||
method: 'GET'
|
||||
});
|
||||
let url = `${pathPrefix}/elasticsearch/command`;
|
||||
let args = buildQueryArgs({
|
||||
keyword: params.keyword,
|
||||
from: params.from,
|
||||
size: params.size,
|
||||
});
|
||||
if (args.length > 0) {
|
||||
url += args;
|
||||
}
|
||||
return request(url, {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteCommand(params) {
|
||||
const url = `${pathPrefix}/elasticsearch/command/${params.id}`;
|
||||
return request(url, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const url = `${pathPrefix}/elasticsearch/command/${params.id}`;
|
||||
return request(url, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveCommand(params) {
|
||||
const url = `${pathPrefix}/elasticsearch/command/${params.id}`;
|
||||
return request(url, {
|
||||
method: "PUT",
|
||||
body: params,
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue