add discover table view and fixed bugs

This commit is contained in:
liugq 2021-10-11 15:03:43 +08:00
parent 947b0bfa04
commit e1cd6d0cf7
46 changed files with 654 additions and 534 deletions

View File

@ -21,92 +21,91 @@ type docReqBody struct {
} }
func (handler APIHandler) HandleAddDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleAddDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
reqBody := map[string]interface{}{} reqBody := map[string]interface{}{}
resResult := newResponseBody() resBody := newResponseBody()
err := handler.DecodeJSON(req, &reqBody) err := handler.DecodeJSON(req, &reqBody)
if err != nil { if err != nil {
resResult["status"] = false resBody["error"] = err.Error()
resResult["error"] = err.Error() handler.WriteJSON(w, resBody, http.StatusOK)
handler.WriteJSON(w, resResult, http.StatusOK)
return return
} }
indexName := ps.ByName("index") indexName := ps.ByName("index")
id := ps.ByName("id") docID := ps.ByName("docId")
if strings.Trim(id, "/") == "" { if strings.Trim(docID, "/") == "" {
id = util.GetUUID() docID = util.GetUUID()
} }
docType := handler.GetParameter(req, "_type") docType := handler.GetParameter(req, "_type")
_, err = client.Index(indexName, docType, id, reqBody) insertRes, err := client.Index(indexName, docType, docID, reqBody)
if err != nil { if err != nil {
resResult["status"] = false resBody["error"] = err
resResult["error"] = err handler.WriteJSON(w, resBody, http.StatusOK)
handler.WriteJSON(w, resResult, http.StatusOK)
return return
} }
reqBody["id"] = id reqBody["_id"] = docID
resResult["payload"] = reqBody resBody["result"] = insertRes.Result
handler.WriteJSON(w, resResult, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleUpdateDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleUpdateDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
resBody := newResponseBody()
if client == nil {
resBody["error"] = "can not found target cluster"
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
reqBody := map[string]interface{}{} reqBody := map[string]interface{}{}
resResult := newResponseBody()
err := handler.DecodeJSON(req, &reqBody) err := handler.DecodeJSON(req, &reqBody)
if err != nil { if err != nil {
resResult["status"] = false resBody["error"] = err
resResult["error"] = err handler.WriteJSON(w, resBody, http.StatusOK)
handler.WriteJSON(w, resResult, http.StatusOK)
return return
} }
indexName := ps.ByName("index") indexName := ps.ByName("index")
id := ps.ByName("id") docID := ps.ByName("docId")
typ := handler.GetParameter(req, "_type") typ := handler.GetParameter(req, "_type")
resp, err := client.Get(indexName,typ, id) insertRes, err := client.Index(indexName, typ, docID, reqBody)
if err != nil { if err != nil {
resResult["status"] = false resBody["error"] = err.Error()
resResult["error"] = err.Error() handler.WriteJSON(w, resBody, http.StatusOK)
handler.WriteJSON(w, resResult, http.StatusOK)
return return
} }
source := resp.Source resBody["_source"] = reqBody
for k, v := range reqBody { resBody["_id"] = docID
if k == "id" { resBody["result"] = insertRes.Result
continue handler.WriteJSON(w, resBody, http.StatusOK)
}
source[k] = v
}
_, err = client.Index(indexName, typ, id, source)
if err != nil {
resResult["status"] = false
resResult["error"] = err.Error()
handler.WriteJSON(w, resResult, http.StatusOK)
return
}
resResult["payload"] = reqBody
handler.WriteJSON(w, resResult, http.StatusOK)
} }
func (handler APIHandler) HandleDeleteDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleDeleteDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
resResult := newResponseBody() client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") resBody := newResponseBody()
id := ps.ByName("id") if client == nil {
typ := handler.GetParameter(req, "_type") resBody["error"] = "can not found target cluster"
_, err := client.Delete(indexName, typ, id) handler.WriteJSON(w, resBody, http.StatusOK)
if err != nil {
resResult["error"] = err.Error()
resResult["status"] = false
handler.WriteJSON(w, resResult, http.StatusOK)
return return
} }
resResult["payload"] = true
handler.WriteJSON(w, resResult, http.StatusOK) indexName := ps.ByName("index")
docID := ps.ByName("docId")
typ := handler.GetParameter(req, "_type")
delRes, err := client.Delete(indexName, typ, docID, "wait_for")
if err != nil {
resBody["error"] = err.Error()
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
resBody["result"] = delRes.Result
handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleSearchDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleSearchDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
reqBody := docReqBody{} reqBody := docReqBody{}
resResult := newResponseBody() resResult := newResponseBody()
err := handler.DecodeJSON(req, &reqBody) err := handler.DecodeJSON(req, &reqBody)

View File

@ -2,13 +2,13 @@ package index_management
import ( import (
"fmt" "fmt"
log "github.com/cihub/seelog"
httprouter "infini.sh/framework/core/api/router" httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic" "infini.sh/framework/core/elastic"
"infini.sh/framework/core/orm" "infini.sh/framework/core/orm"
"infini.sh/framework/core/util" "infini.sh/framework/core/util"
"infini.sh/framework/modules/elastic/common" "infini.sh/framework/modules/elastic/common"
"net/http" "net/http"
log "github.com/cihub/seelog"
) )
func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
@ -49,6 +49,7 @@ func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req
} }
return true return true
}) })
resBody := util.MapStr{ resBody := util.MapStr{
"total_node": totalNode, "total_node": totalNode,
"total_store_size_in_bytes": totalStoreSize, "total_store_size_in_bytes": totalStoreSize,

View File

@ -1,15 +1,15 @@
package index_management package index_management
import ( import (
"net/http"
"strings"
httprouter "infini.sh/framework/core/api/router" httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic" "infini.sh/framework/core/elastic"
"infini.sh/framework/core/util"
"net/http"
) )
func (handler APIHandler) HandleGetMappingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleGetMappingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") indexName := ps.ByName("index")
resBody := newResponseBody() resBody := newResponseBody()
var copyAll = false var copyAll = false
@ -20,114 +20,102 @@ func (handler APIHandler) HandleGetMappingsAction(w http.ResponseWriter, req *ht
_, _, idxs, err := client.GetMapping(copyAll, indexName) _, _, idxs, err := client.GetMapping(copyAll, indexName)
if err != nil { if err != nil {
resBody["error"] = err resBody["error"] = err
resBody["status"] = false
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
if copyAll { //if copyAll {
for key, _ := range *idxs { // for key, _ := range *idxs {
if strings.HasPrefix(key, ".") || strings.HasPrefix(key, "infini-") { // if strings.HasPrefix(key, ".") || strings.HasPrefix(key, "infini-") {
delete(*idxs, key) // delete(*idxs, key)
} // }
} // }
} //}
resBody["payload"] = idxs handler.WriteJSON(w, idxs, http.StatusOK)
handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleGetIndicesAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleGetIndicesAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
catIndices, err := client.GetIndices("") catIndices, err := client.GetIndices("")
for key, _ := range *catIndices { resBody := util.MapStr{}
if strings.HasPrefix(key,".") || strings.HasPrefix(key, "infini-"){
delete(*catIndices, key)
}
}
resBody := newResponseBody()
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = catIndices handler.WriteJSON(w, catIndices, http.StatusOK)
handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleGetSettingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleGetSettingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") indexName := ps.ByName("index")
resBody := newResponseBody() resBody := newResponseBody()
indexes, err := client.GetIndexSettings(indexName) indexes, err := client.GetIndexSettings(indexName)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = indexes handler.WriteJSON(w, indexes, http.StatusOK)
handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleUpdateSettingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleUpdateSettingsAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") indexName := ps.ByName("index")
settings := map[string]interface{}{} settings := map[string]interface{}{}
resBody := newResponseBody() resBody := newResponseBody()
err := handler.DecodeJSON(req, &settings) err := handler.DecodeJSON(req, &settings)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
err = client.UpdateIndexSettings(indexName, settings) err = client.UpdateIndexSettings(indexName, settings)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = true resBody["result"] = "updated"
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleDeleteIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleDeleteIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") indexName := ps.ByName("index")
resBody := newResponseBody() resBody := newResponseBody()
err := client.DeleteIndex(indexName) err := client.DeleteIndex(indexName)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = true resBody["result"] = "deleted"
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
} }
func (handler APIHandler) HandleCreateIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { func (handler APIHandler) HandleCreateIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch) targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
indexName := ps.ByName("index") indexName := ps.ByName("index")
resBody := newResponseBody() resBody := newResponseBody()
config := map[string]interface{}{} config := map[string]interface{}{}
err := handler.DecodeJSON(req, &config) err := handler.DecodeJSON(req, &config)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
err = client.CreateIndex(indexName, config) err = client.CreateIndex(indexName, config)
if err != nil { if err != nil {
resBody["status"] = false
resBody["error"] = err resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
return return
} }
resBody["payload"] = true resBody["result"] = "created"
handler.WriteJSON(w, resBody, http.StatusOK) handler.WriteJSON(w, resBody, http.StatusOK)
} }

View File

@ -16,6 +16,7 @@ func Init(cfg *config.AppConfig) {
Config: cfg, Config: cfg,
} }
var pathPrefix = "/_search-center/" var pathPrefix = "/_search-center/"
var esPrefix = "/elasticsearch/:id/"
//ui.HandleUIMethod(api.POST, "/api/get_indices",index_management.API1) //ui.HandleUIMethod(api.POST, "/api/get_indices",index_management.API1)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/overview"), handler.ElasticsearchOverviewAction) ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/overview"), handler.ElasticsearchOverviewAction)
@ -24,20 +25,21 @@ func Init(cfg *config.AppConfig) {
//ui.HandleUIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction) //ui.HandleUIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "dict/:id"), handler.DeleteDictItemAction) ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "dict/:id"), handler.DeleteDictItemAction)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix, "dict/:id"), handler.UpdateDictItemAction) ui.HandleUIMethod(api.PUT, path.Join(pathPrefix, "dict/:id"), handler.UpdateDictItemAction)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "doc/:index/_search"), handler.HandleSearchDocumentAction) ui.HandleUIMethod(api.POST, path.Join(esPrefix, "doc/:index/_search"), handler.HandleSearchDocumentAction)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "doc/:index/_create"), handler.HandleAddDocumentAction) ui.HandleUIMethod(api.POST, path.Join(esPrefix, "doc/:index"), handler.HandleAddDocumentAction)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix, "doc/:index/:id"), handler.HandleUpdateDocumentAction) ui.HandleUIMethod(api.PUT, path.Join(esPrefix, "doc/:index/:docId"), handler.HandleUpdateDocumentAction)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "doc/:index/:id"), handler.HandleDeleteDocumentAction) ui.HandleUIMethod(api.DELETE, path.Join(esPrefix, "doc/:index/:docId"), handler.HandleDeleteDocumentAction)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "rebuild/*id"), handler.HandleReindexAction) ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "rebuild/*id"), handler.HandleReindexAction)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "rebuild/_search"), handler.HandleGetRebuildListAction) ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "rebuild/_search"), handler.HandleGetRebuildListAction)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "rebuild/:id"), handler.HandleDeleteRebuildAction) ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "rebuild/:id"), handler.HandleDeleteRebuildAction)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "_cat/indices"), handler.HandleGetIndicesAction)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "index/:index/_mappings"), handler.HandleGetMappingsAction) ui.HandleUIMethod(api.GET, path.Join(esPrefix, "_cat/indices"), handler.HandleGetIndicesAction)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "index/:index/_settings"), handler.HandleGetSettingsAction) ui.HandleUIMethod(api.GET, path.Join(esPrefix, "index/:index/_mappings"), handler.HandleGetMappingsAction)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix, "index/:index/_settings"), handler.HandleUpdateSettingsAction) ui.HandleUIMethod(api.GET, path.Join(esPrefix, "index/:index/_settings"), handler.HandleGetSettingsAction)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "index/:index"), handler.HandleDeleteIndexAction) ui.HandleUIMethod(api.PUT, path.Join(esPrefix, "index/:index/_settings"), handler.HandleUpdateSettingsAction)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "index/:index"), handler.HandleCreateIndexAction) ui.HandleUIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), handler.HandleDeleteIndexAction)
ui.HandleUIMethod(api.POST, path.Join(esPrefix, "index/:index"), handler.HandleCreateIndexAction)
//new api //new api
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview) ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)

View File

@ -139,25 +139,6 @@ export default [
name: 'data', name: 'data',
icon: 'database', icon: 'database',
routes: [ routes: [
// {
// path: '/data/pipes',
// name: 'pipes',
// component: './DataManagement/Pipes',
// routes: [
// {
// path: '/data/pipes',
// redirect: '/data/pipes/logstash',
// },
// {
// path: '/data/pipes/logstash',
// component: './DataManagement/LogstashConfig',
// },
// {
// path: '/data/pipes/ingestpipeline',
// component: './DataManagement/IngestPipeline',
// },
// ]
// },
// { // {
// path: '/data/overview', // path: '/data/overview',
// name: 'overview', // name: 'overview',
@ -165,14 +146,16 @@ export default [
// routes:[ // routes:[
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
// }, { // },
// path: '/data/index', {
// name: 'index', path: '/data/index',
// component: './DataManagement/Index', name: 'index',
// routes:[ component: './DataManagement/Index',
// { path: '/', redirect: '/' }, routes:[
// ], { path: '/', redirect: '/' },
// },{ ],
},
// {
// path: '/data/document', // path: '/data/document',
// name: 'document', // name: 'document',
// component: './DataManagement/Document', // component: './DataManagement/Document',
@ -269,9 +252,9 @@ export default [
// { // {
// path: '/search/alias', // path: '/search/alias',
// redirect: '/search/alias/index', // redirect: '/search/alias/index',
// routes:[ // // routes:[
// { path: '/', redirect: '/' }, // // { path: '/', redirect: '/' },
// ], // // ],
// }, // },
// { // {
// path: '/search/alias/index', // path: '/search/alias/index',
@ -296,9 +279,9 @@ export default [
// { // {
// path: '/search/dict', // path: '/search/dict',
// redirect: '/search/dict/professional', // redirect: '/search/dict/professional',
// routes:[ // // routes:[
// { path: '/', redirect: '/' }, // // { path: '/', redirect: '/' },
// ], // // ],
// }, // },
// { // {
// path: '/search/dict/professional', // path: '/search/dict/professional',

View File

@ -10,10 +10,10 @@
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.1.2",
"@elastic/charts": "^25.0.1", "@elastic/charts": "^25.0.1",
"@elastic/datemath": "^5.0.3", "@elastic/datemath": "^5.0.3",
"@elastic/eui": "^34.4.0", "@elastic/eui": "34.4.0",
"@elastic/numeral": "^2.5.1", "@elastic/numeral": "^2.5.1",
"@hapi/boom": "^9.1.3", "@hapi/boom": "^9.1.3",
"@monaco-editor/react": "^3.7.4", "@monaco-editor/react": "^4.2.2",
"@svgdotjs/svg.js": "^3.0.16", "@svgdotjs/svg.js": "^3.0.16",
"antd": "^3.26.18", "antd": "^3.26.18",
"antd-table-infinity": "^1.1.6", "antd-table-infinity": "^1.1.6",

View File

@ -57,7 +57,7 @@ interface SavedObjectBody {
type FormatFieldFn = (hit: Record<string, any>, fieldName: string) => any; type FormatFieldFn = (hit: Record<string, any>, fieldName: string) => any;
export class IndexPattern implements IIndexPattern { export class IndexPattern implements IIndexPattern {
public id?: string; public id: string;
public title: string = ''; public title: string = '';
public viewName: string = ''; public viewName: string = '';
public fieldFormatMap: Record<string, any>; public fieldFormatMap: Record<string, any>;

View File

@ -174,7 +174,7 @@ export interface FieldSpec {
export type IndexPatternFieldMap = Record<string, FieldSpec>; export type IndexPatternFieldMap = Record<string, FieldSpec>;
export interface IndexPatternSpec { export interface IndexPatternSpec {
id?: string; id: string;
version?: string; version?: string;
title?: string; title?: string;
intervalName?: string; intervalName?: string;

View File

@ -15,12 +15,14 @@ interface TableProps {
onChangeSortOrder?: (sortOrder: SortOrder[]) => void; onChangeSortOrder?: (sortOrder: SortOrder[]) => void;
onMoveColumn?: (name: string, index: number) => void; onMoveColumn?: (name: string, index: number) => void;
onRemoveColumn?: (name: string) => void; onRemoveColumn?: (name: string) => void;
onAddColumn?: (name: string) => void;
document: any;
} }
const pageCount = 50; const pageCount = 50;
const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, onFilter, onMoveColumn, const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, onFilter, onMoveColumn, onAddColumn,
onRemoveColumn, onChangeSortOrder }) => { onRemoveColumn, onChangeSortOrder, document }) => {
const [scrollState, setScrollState] = useState({limit: pageCount, hasMore: true}); const [scrollState, setScrollState] = useState({limit: pageCount, hasMore: true});
useEffect(()=>{ useEffect(()=>{
setScrollState({ setScrollState({
@ -46,7 +48,7 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
<div> <div>
{hits.length ? ( {hits.length ? (
<div> <div>
<table className="kbn-table table" data-test-subj="docTable"> <table className="kbn-table table">
<thead> <thead>
<TableHeader columns={columns} <TableHeader columns={columns}
defaultSortOrder={''} defaultSortOrder={''}
@ -66,9 +68,10 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
hideTimeColumn={false} hideTimeColumn={false}
indexPattern={indexPattern} indexPattern={indexPattern}
isShortDots={false} isShortDots={false}
onAddColumn={()=>{}} onAddColumn={onAddColumn}
onRemoveColumn={()=>{}} onRemoveColumn={onRemoveColumn}
row={row} row={row}
document={document}
/> />
})} })}
</tbody> </tbody>

View File

@ -1,5 +1,14 @@
import {EuiIcon} from '@elastic/eui'; import {EuiIcon} from '@elastic/eui';
import { DocViewer } from '../../doc_viewer/doc_viewer'; import { DocViewer } from '../../doc_viewer/doc_viewer';
import {Drawer, Button, Menu,Dropdown, Icon, Popconfirm, message,Descriptions, Popover, Input} from 'antd';
import Editor from "@monaco-editor/react";
import {useState, useRef} from 'react';
function generateNewID(id: string) {
return id.slice(0, 14) + Math.random().toString(36).substr(2, 6)
}
interface Props { interface Props {
columns: string[]; columns: string[];
@ -8,6 +17,7 @@ interface Props {
onFilter: (field: any, values: any, operation: any) => void; onFilter: (field: any, values: any, operation: any) => void;
onAddColumn?: (name: string) => void; onAddColumn?: (name: string) => void;
onRemoveColumn?: (name: string) => void; onRemoveColumn?: (name: string) => void;
document: any;
} }
export function Detail({ export function Detail({
@ -17,7 +27,59 @@ export function Detail({
onFilter, onFilter,
onAddColumn, onAddColumn,
onRemoveColumn, onRemoveColumn,
document,
}:Props){ }:Props){
const [editorVisible, setEditorVisble] = useState(false);
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
editorRef.current = editor;
}
const editDocumentClick = ()=>{
setEditorVisble(true)
}
const editCancelClick = ()=>{
setEditorVisble(false)
}
const saveDocumentClick = async (docID?: string)=>{
const value = editorRef.current?.getValue();
let source = {}
try {
source = JSON.parse(value)
} catch (error) {
message.error('wrong json format')
return
}
const res = await document.saveDocument({
_index: row._index,
_id: docID || row._id,
_type: row._type,
_source: source
})
if(!res.error) setEditorVisble(false)
}
const deleteDocumentClick = ()=>{
document.deleteDocument({
_index: row._index,
_id: row._id,
_type: row._type,
})
}
const menu = (
<Menu>
<Menu.Item key="Edit" onClick={editDocumentClick}>
<a> Edit </a>
</Menu.Item>
<Menu.Item key="Delete">
<Popconfirm title="sure to delete" onConfirm={()=>{
deleteDocumentClick();
}}><a> Delete </a></Popconfirm>
</Menu.Item>
</Menu>
);
return ( return (
<td colSpan={ columns.length + 2 }> <td colSpan={ columns.length + 2 }>
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween"> <div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
@ -34,9 +96,49 @@ export function Detail({
</div> </div>
</div> </div>
</div> </div>
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small"> <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow"> <div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow">
<Drawer title="Edit document" visible={editorVisible} width="640" destroyOnClose={true}
onClose={()=>{setEditorVisble(false)}}>
<Descriptions>
<Descriptions.Item label="_index">{row._index}</Descriptions.Item>
<Descriptions.Item label="_id">{row._id}</Descriptions.Item>
</Descriptions>
<Editor
height="70vh"
theme="vs-light"
language="json"
options={{
minimap: {
enabled: false,
},
tabSize: 2,
wordBasedSuggestions: true,
}}
value={JSON.stringify(row._source, null, 2)}
onMount={handleEditorDidMount}
/>
<div style={{display:'flex', height: '10vh', alignItems:'center', justifyContent:'center'}}>
<div style={{marginLeft:'auto'}} >
<Button onClick={editCancelClick} style={{marginRight:5}}>Cancel</Button>
{/* <Button type="primary" onClick={()=>{}} style={{marginRight:5}}>Save as New</Button> */}
<SaveAsNewButton docID={row._id} saveDocumentClick={saveDocumentClick}/>
<Button type="primary" onClick={()=>{saveDocumentClick()}} >Save</Button>
</div>
</div>
</Drawer>
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small"> <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
{/* <a
className="euiLink"
onClick={()=>{setEditorVisble(true)}}
>Edit document</a> */}
<Dropdown overlay={menu} >
<a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
Operation <Icon type="down" />
</a>
</Dropdown>
</div>
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
<a <a
className="euiLink" className="euiLink"
>View surrounding documents</a> >View surrounding documents</a>
@ -45,9 +147,9 @@ export function Detail({
<a <a
className="euiLink" className="euiLink"
>View single document</a> >View single document</a>
</div> </div> */}
</div> </div>
</div> */} </div>
</div> </div>
<div data-test-subj="docViewer"> <div data-test-subj="docViewer">
<DocViewer <DocViewer
@ -62,5 +164,29 @@ export function Detail({
</td> </td>
)
}
const SaveAsNewButton = ({docID, saveDocumentClick}:any)=>{
const newID = generateNewID(docID);
const [newDocID, setNewDocID] = useState(newID)
const content = (<div style={{width: 200}}>
<div><Input value={newDocID} onChange={(e)=>{
setNewDocID(e.target.value)
}} /></div>
<div style={{marginTop:10}}><Button onClick={()=>{
saveDocumentClick(newDocID)
}}></Button></div>
</div>)
return (
<Popover
content={content}
title="Please input new ID"
trigger="click"
// visible={this.state.visible}
// onVisibleChange={this.handleVisibleChange}
>
<Button style={{marginRight:5}} type="primary">Save as new</Button>
</Popover>
) )
} }

View File

@ -13,6 +13,7 @@ interface Props {
onAddColumn?: (name: string) => void; onAddColumn?: (name: string) => void;
onRemoveColumn?: (name: string) => void; onRemoveColumn?: (name: string) => void;
row: any; row: any;
document: any;
} }
export function TableRow({ export function TableRow({
@ -24,6 +25,7 @@ export function TableRow({
onAddColumn, onAddColumn,
onRemoveColumn, onRemoveColumn,
row, row,
document
}:Props){ }:Props){
const mapping = indexPattern.fields.getByName; const mapping = indexPattern.fields.getByName;
const [open,setOpen] = useState(false); const [open,setOpen] = useState(false);
@ -60,6 +62,7 @@ export function TableRow({
<Detail columns={columns} <Detail columns={columns}
indexPattern={indexPattern} indexPattern={indexPattern}
row={row} row={row}
document={document}
onFilter={onFilter} onFilter={onFilter}
onAddColumn={onAddColumn} onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}/> onRemoveColumn={onRemoveColumn}/>

View File

@ -1,6 +1,10 @@
@import '../../../../../core/public/variables.scss'; @import '../../../../../core/public/variables.scss';
.kbnDocViewerTable { .kbnDocViewerTable {
margin-top: $euiSizeS; margin-top: $euiSizeS;
font-size: 12px;
.kbnDocViewer__buttons{
padding-top: 0px !important;
}
} }
.kbnDocViewer { .kbnDocViewer {

View File

@ -51,8 +51,8 @@ export class DocViewerTab extends React.Component<Props, State> {
shouldComponentUpdate(nextProps: Props, nextState: State) { shouldComponentUpdate(nextProps: Props, nextState: State) {
return ( return (
nextProps.renderProps.hit._id !== this.props.renderProps.hit._id || nextProps.renderProps.hit !== this.props.renderProps.hit ||
nextProps.id !== this.props.id || nextProps.id !== this.props.id || nextProps.renderProps.columns !== this.props.renderProps.columns ||
nextState.hasError nextState.hasError
); );
} }

View File

@ -100,7 +100,7 @@ export function DocViewTableRow({
<DocViewTableRowBtnCollapse onClick={onToggleCollapse} isCollapsed={isCollapsed} /> <DocViewTableRowBtnCollapse onClick={onToggleCollapse} isCollapsed={isCollapsed} />
)} )}
{displayUnderscoreWarning && <DocViewTableRowIconUnderscore />} {displayUnderscoreWarning && <DocViewTableRowIconUnderscore />}
{displayNoMappingWarning && <DocViewTableRowIconNoMapping />} {/* {displayNoMappingWarning && <DocViewTableRowIconNoMapping />} */}
<div <div
className={valueClassName} className={valueClassName}
data-test-subj={`tableDocViewRow-${field}-value`} data-test-subj={`tableDocViewRow-${field}-value`}

View File

@ -17,7 +17,6 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
export interface Props { export interface Props {
@ -26,9 +25,7 @@ export interface Props {
} }
export function DocViewTableRowBtnCollapse({ onClick, isCollapsed }: Props) { export function DocViewTableRowBtnCollapse({ onClick, isCollapsed }: Props) {
const label = i18n.translate('discover.docViews.table.toggleFieldDetails', { const label = 'Toggle field details';
defaultMessage: 'Toggle field details',
});
return ( return (
<EuiToolTip content={label}> <EuiToolTip content={label}>
<EuiButtonIcon <EuiButtonIcon

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export interface Props { export interface Props {
onClick: () => void; onClick: () => void;
@ -28,23 +26,15 @@ export interface Props {
export function DocViewTableRowBtnFilterAdd({ onClick, disabled = false }: Props) { export function DocViewTableRowBtnFilterAdd({ onClick, disabled = false }: Props) {
const tooltipContent = disabled ? ( const tooltipContent = disabled ? (
<FormattedMessage "Unindexed fields can not be searched"
id="discover.docViews.table.unindexedFieldsCanNotBeSearchedTooltip"
defaultMessage="Unindexed fields can not be searched"
/>
) : ( ) : (
<FormattedMessage "Filter for value"
id="discover.docViews.table.filterForValueButtonTooltip"
defaultMessage="Filter for value"
/>
); );
return ( return (
<EuiToolTip content={tooltipContent}> <EuiToolTip content={tooltipContent}>
<EuiButtonIcon <EuiButtonIcon
aria-label={i18n.translate('discover.docViews.table.filterForValueButtonAriaLabel', { aria-label='Filter for value'
defaultMessage: 'Filter for value',
})}
className="kbnDocViewer__actionButton" className="kbnDocViewer__actionButton"
data-test-subj="addInclusiveFilterButton" data-test-subj="addInclusiveFilterButton"
disabled={disabled} disabled={disabled}

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export interface Props { export interface Props {
onClick: () => void; onClick: () => void;
@ -34,29 +32,18 @@ export function DocViewTableRowBtnFilterExists({
}: Props) { }: Props) {
const tooltipContent = disabled ? ( const tooltipContent = disabled ? (
scripted ? ( scripted ? (
<FormattedMessage "Unable to filter for presence of scripted fields"
id="discover.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip"
defaultMessage="Unable to filter for presence of scripted fields"
/>
) : ( ) : (
<FormattedMessage "Unable to filter for presence of meta fields"
id="discover.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip"
defaultMessage="Unable to filter for presence of meta fields"
/>
) )
) : ( ) : (
<FormattedMessage "Filter for field present"
id="discover.docViews.table.filterForFieldPresentButtonTooltip"
defaultMessage="Filter for field present"
/>
); );
return ( return (
<EuiToolTip content={tooltipContent}> <EuiToolTip content={tooltipContent}>
<EuiButtonIcon <EuiButtonIcon
aria-label={i18n.translate('discover.docViews.table.filterForFieldPresentButtonAriaLabel', { aria-label='Filter for field present'
defaultMessage: 'Filter for field present',
})}
onClick={onClick} onClick={onClick}
className="kbnDocViewer__actionButton" className="kbnDocViewer__actionButton"
data-test-subj="addExistsFilterButton" data-test-subj="addExistsFilterButton"

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export interface Props { export interface Props {
onClick: () => void; onClick: () => void;
@ -28,23 +26,15 @@ export interface Props {
export function DocViewTableRowBtnFilterRemove({ onClick, disabled = false }: Props) { export function DocViewTableRowBtnFilterRemove({ onClick, disabled = false }: Props) {
const tooltipContent = disabled ? ( const tooltipContent = disabled ? (
<FormattedMessage "Unindexed fields can not be searched"
id="discover.docViews.table.unindexedFieldsCanNotBeSearchedTooltip"
defaultMessage="Unindexed fields can not be searched"
/>
) : ( ) : (
<FormattedMessage "Filter out value"
id="discover.docViews.table.filterOutValueButtonTooltip"
defaultMessage="Filter out value"
/>
); );
return ( return (
<EuiToolTip content={tooltipContent}> <EuiToolTip content={tooltipContent}>
<EuiButtonIcon <EuiButtonIcon
aria-label={i18n.translate('discover.docViews.table.filterOutValueButtonAriaLabel', { aria-label= 'Filter out value'
defaultMessage: 'Filter out value',
})}
className="kbnDocViewer__actionButton" className="kbnDocViewer__actionButton"
data-test-subj="removeInclusiveFilterButton" data-test-subj="removeInclusiveFilterButton"
disabled={disabled} disabled={disabled}

View File

@ -17,9 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export interface Props { export interface Props {
active: boolean; active: boolean;
@ -31,9 +29,7 @@ export function DocViewTableRowBtnToggleColumn({ onClick, active, disabled = fal
if (disabled) { if (disabled) {
return ( return (
<EuiButtonIcon <EuiButtonIcon
aria-label={i18n.translate('discover.docViews.table.toggleColumnInTableButtonAriaLabel', { aria-label= 'Toggle column in table'
defaultMessage: 'Toggle column in table',
})}
className="kbnDocViewer__actionButton" className="kbnDocViewer__actionButton"
data-test-subj="toggleColumnButton" data-test-subj="toggleColumnButton"
disabled disabled
@ -44,17 +40,10 @@ export function DocViewTableRowBtnToggleColumn({ onClick, active, disabled = fal
} }
return ( return (
<EuiToolTip <EuiToolTip
content={ content="Toggle column in table"
<FormattedMessage
id="discover.docViews.table.toggleColumnInTableButtonTooltip"
defaultMessage="Toggle column in table"
/>
}
> >
<EuiButtonIcon <EuiButtonIcon
aria-label={i18n.translate('discover.docViews.table.toggleColumnInTableButtonAriaLabel', { aria-label= 'Toggle column in table'
defaultMessage: 'Toggle column in table',
})}
aria-pressed={active} aria-pressed={active}
onClick={onClick} onClick={onClick}
className="kbnDocViewer__actionButton" className="kbnDocViewer__actionButton"

View File

@ -18,19 +18,11 @@
*/ */
import React from 'react'; import React from 'react';
import { EuiIconTip } from '@elastic/eui'; import { EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export function DocViewTableRowIconNoMapping() { export function DocViewTableRowIconNoMapping() {
const ariaLabel = i18n.translate('discover.docViews.table.noCachedMappingForThisFieldAriaLabel', { const ariaLabel = 'Warning';
defaultMessage: 'Warning', const tooltipContent =
}); 'No cached mapping for this field. Refresh field list from the Management > Index Patterns page';
const tooltipContent = i18n.translate(
'discover.docViews.table.noCachedMappingForThisFieldTooltip',
{
defaultMessage:
'No cached mapping for this field. Refresh field list from the Management > Index Patterns page',
}
);
return ( return (
<EuiIconTip <EuiIconTip
aria-label={ariaLabel} aria-label={ariaLabel}

View File

@ -18,22 +18,10 @@
*/ */
import React from 'react'; import React from 'react';
import { EuiIconTip } from '@elastic/eui'; import { EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export function DocViewTableRowIconUnderscore() { export function DocViewTableRowIconUnderscore() {
const ariaLabel = i18n.translate( const ariaLabel = 'Warning';
'discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel', const tooltipContent = 'Field names beginning with _ are not supported';
{
defaultMessage: 'Warning',
}
);
const tooltipContent = i18n.translate(
'discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip',
{
defaultMessage: 'Field names beginning with {underscoreSign} are not supported',
values: { underscoreSign: '_' },
}
);
return ( return (
<EuiIconTip <EuiIconTip

View File

@ -26,6 +26,7 @@ import { createGetterSetter } from '../../kibana_utils/common/';
import { search } from '../../data/public'; import { search } from '../../data/public';
import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry';
import { JsonCodeBlock } from './application/components/json_code_block/json_code_block'; import { JsonCodeBlock } from './application/components/json_code_block/json_code_block';
import { DocViewTable } from './application/components/table/table';
let angularModule: any = null; let angularModule: any = null;
let services: DiscoverServices | null = null; let services: DiscoverServices | null = null;
@ -74,11 +75,11 @@ export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter<Doc
const docViewsRegistry = new DocViewsRegistry(); const docViewsRegistry = new DocViewsRegistry();
setDocViewsRegistry(docViewsRegistry); setDocViewsRegistry(docViewsRegistry);
// docViewsRegistry.addDocView({ docViewsRegistry.addDocView({
// title: 'Table', title: 'Table',
// order: 10, order: 10,
// component: DocViewTable, component: DocViewTable,
// }); });
docViewsRegistry.addDocView({ docViewsRegistry.addDocView({
title: 'JSON', title: 'JSON',
order: 20, order: 20,

View File

@ -46,6 +46,7 @@ import clusterBg from '@/assets/cluster_bg.png';
export interface EditIndexPatternProps extends RouteComponentProps { export interface EditIndexPatternProps extends RouteComponentProps {
indexPattern: IndexPattern; indexPattern: IndexPattern;
id: string;
} }
const mappingAPILink = 'Mapping API'; const mappingAPILink = 'Mapping API';
@ -65,7 +66,7 @@ const confirmModalOptionsDelete = {
}; };
export const EditIndexPattern = withRouter( export const EditIndexPattern = withRouter(
({ indexPattern, history, location }: EditIndexPatternProps) => { ({ indexPattern, history, location, id }: EditIndexPatternProps) => {
const { const {
uiSettings, uiSettings,
indexPatternManagementStart, indexPatternManagementStart,
@ -131,8 +132,8 @@ export const EditIndexPattern = withRouter(
uiSettings.set('defaultIndex', otherPatterns[0].id); uiSettings.set('defaultIndex', otherPatterns[0].id);
} }
} }
if (indexPattern.id) { if (indexPattern.id || id) {
Promise.resolve(data.indexPatterns.delete(indexPattern.id)).then(function () { Promise.resolve(data.indexPatterns.delete(indexPattern.id || id)).then(function () {
history.push(''); history.push('');
}); });
} }

View File

@ -41,7 +41,7 @@ const EditIndexPatternCont: React.FC<RouteComponentProps<{ id: string }>> = ({ .
}, [data.indexPatterns, props.match.params.id, ]); //setBreadcrumbs }, [data.indexPatterns, props.match.params.id, ]); //setBreadcrumbs
if (indexPattern) { if (indexPattern) {
return <EditIndexPattern indexPattern={indexPattern} />; return <EditIndexPattern indexPattern={indexPattern} id={indexPattern.id} />;
} else { } else {
return <></>; return <></>;
} }

View File

@ -20,6 +20,7 @@
import React from 'react'; import React from 'react';
import { EuiFlexGroup, EuiToolTip, EuiFlexItem, EuiTitle, EuiButtonIcon } from '@elastic/eui'; import { EuiFlexGroup, EuiToolTip, EuiFlexItem, EuiTitle, EuiButtonIcon } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/public'; import { IIndexPattern } from 'src/plugins/data/public';
import {Popconfirm} from 'antd';
interface IndexHeaderProps { interface IndexHeaderProps {
indexPattern: IIndexPattern; indexPattern: IIndexPattern;
@ -88,13 +89,15 @@ export function IndexHeader({
{deleteIndexPatternClick && ( {deleteIndexPatternClick && (
<EuiFlexItem> <EuiFlexItem>
<EuiToolTip content={removeTooltip}> <EuiToolTip content={removeTooltip}>
<EuiButtonIcon <Popconfirm title="确定要删除?" onConfirm={deleteIndexPatternClick}>
color="danger" <EuiButtonIcon
onClick={deleteIndexPatternClick} color="danger"
iconType="trash" // onClick={deleteIndexPatternClick}
aria-label={removeAriaLabel} iconType="trash"
data-test-subj="deleteIndexPatternButton" aria-label={removeAriaLabel}
/> data-test-subj="deleteIndexPatternButton"
/>
</Popconfirm>
</EuiToolTip> </EuiToolTip>
</EuiFlexItem> </EuiFlexItem>
)} )}

View File

@ -1,7 +1,9 @@
import {List} from 'antd'; import {List, ConfigProvider, Button} from 'antd';
import {AlertItem, AlertRecord} from './AlertItem'; import {AlertItem, AlertRecord} from './AlertItem';
import './alertlist.scss'; import './alertlist.scss';
import {Legend, LegendItem} from './Legend'; import {Legend, LegendItem} from './Legend';
import {router} from 'umi';
interface AlertListProps { interface AlertListProps {
dataSource: AlertRecord[]; dataSource: AlertRecord[];
@ -9,6 +11,7 @@ interface AlertListProps {
title: string; title: string;
onItemClick: (item: AlertRecord)=>void; onItemClick: (item: AlertRecord)=>void;
legendItems?: LegendItem[]; legendItems?: LegendItem[];
onEmptyClick?: ()=>void;
} }
export const AlertList = ({ export const AlertList = ({
@ -16,38 +19,54 @@ export const AlertList = ({
pagination, pagination,
title, title,
onItemClick, onItemClick,
legendItems legendItems,
onEmptyClick,
}: AlertListProps)=>{ }: AlertListProps)=>{
if(typeof onEmptyClick !== 'function'){
onEmptyClick = ()=>{
router.push('/alerting/monitor/create-monitor');
}
}
const AlertRenderEmpty = ()=>{
return <div>
<Button type="primary" onClick={onEmptyClick}></Button>
</div>
}
return ( return (
<div className="alert-list"> <div className="alert-list-layout">
<div className="header"> <div className="title">
<div className="title"> {title}
{title} <span className="total">({pagination?.total})</span>
<span className="total">({pagination?.total})</span> </div>
</div> <div className="alert-list">
{ <div className="header">
legendItems ? ( <div className="legend"> {
<Legend items={legendItems}/> legendItems ? ( <div className="legend">
</div>):null <Legend items={legendItems}/>
} </div>):null
}
</div>
<List </div>
itemLayout="vertical" <ConfigProvider renderEmpty={AlertRenderEmpty}>
size="large" <List
pagination={{ itemLayout="vertical"
onChange: page => { size="large"
console.log(page); pagination={{
}, onChange: page => {
pageSize: 20, console.log(page);
...pagination, },
}} pageSize: 20,
dataSource={dataSource} ...pagination,
renderItem={item => ( }}
<AlertItem item={item} onClick={onItemClick} /> dataSource={dataSource}
)} renderItem={item => (
/> <AlertItem item={item} onClick={onItemClick} />
</div> )}
/>
</ConfigProvider>
</div>
</div>
) )
} }

View File

@ -3,19 +3,22 @@
padding: 10px 5px; padding: 10px 5px;
.header{ .header{
display: flex; display: flex;
.title{
color: #333;
font-weight:600;
padding-bottom: 6px;
.total{
color: #999;
margin-left: 15px;
font-size: 12px;
}
}
.legend{ .legend{
margin-left: auto; margin-left: auto;
} }
margin-bottom: 5px;
}
}
.alert-list-layout{
.title{
color: #333;
font-weight:600;
font-size: 16px;
padding-bottom: 6px;
.total{
color: #999;
margin-left: 15px;
font-size: 12px;
}
} }
} }

View File

@ -76,22 +76,26 @@ export const AlertOverview = (props: any)=>{
return ( return (
<div className="alert-overview"> <div className="alert-overview">
<div className="left"> <div className="left">
<AlertList dataSource={alerts.data as any} <div>
title={formatMessage({id:'alert.overview.alertlist.title'})} <AlertList dataSource={alerts.data}
onItemClick={onItemClick} title={formatMessage({id:'alert.overview.alertlist.title'})}
pagination={{ onItemClick={onItemClick}
pageSize, pagination={{
total: alerts.total, pageSize,
onChange: onAlertPageChange, total: alerts.total,
}}/> onChange: onAlertPageChange,
}}/>
</div>
<div style={{marginTop:10}}>
<AlertList dataSource={historyAlerts.data} <AlertList dataSource={historyAlerts.data}
title={formatMessage({id:'alert.overview.alertlist-history.title'})} title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick} onItemClick={onItemClick}
pagination={{ pagination={{
pageSize, pageSize,
total: historyAlerts.total, total: historyAlerts.total,
onChange: onAlertHistoryPageChange, onChange: onAlertHistoryPageChange,
}}/> }}/>
</div>
</div> </div>
{/* <div className="right"> {/* <div className="right">
<div></div> <div></div>

View File

@ -1,5 +1,5 @@
import React, {useEffect, useState, useMemo} from "react"; import React, {useEffect, useState, useMemo} from "react";
import {Spin, Card} from 'antd'; import {Spin, Card, Empty, Button} from 'antd';
import {Fetch} from '../../../../components/kibana/core/public/http/fetch'; import {Fetch} from '../../../../components/kibana/core/public/http/fetch';
import './overview.scss'; import './overview.scss';
import { import {
@ -148,7 +148,49 @@ export default (props)=>{
</div> </div>
</Card> </Card>
</div> </div>
<div> <div className="charts">
<div style={{height:'150px'}} className="chart">
<Chart>
<Settings theme={theme} />
<Axis id="bottom" position={Position.Bottom} title="Last 3 months" showOverlappingTicks tickFormat={timeFormatter(niceTimeFormatByDay(data.metrics.last_tree_month.day))} />
<Axis
id="left"
title="Alert number"
position={Position.Left}
/>
<LineSeries
id="lines"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={data.metrics.last_tree_month?.data || []}
/>
</Chart>
</div>
<div style={{height:'150px', marginTop: 10}} className="chart">
<Chart>
<Settings showLegend showLegendExtra legendPosition={Position.Right} theme={theme} />
<Axis id="bottom" position={Position.Bottom} title="Top 10 cluster" showOverlappingTicks />
<Axis id="left2" title="Alert number" position={Position.Left} tickFormat={(d) => Number(d).toFixed(0)} />
<BarSeries
id="bars"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
stackAccessors={['x']}
splitSeriesAccessors={['g']}
data={data.metrics.top_ten_cluster?.data || []}
/>
</Chart>
</div>
</div>
<div className="alertlist-item">
{alerts.data.length == 0 && historyAlerts.data.length == 0 && <Empty description=''>
<Button>创建监控项</Button>
</Empty>}
<AlertList dataSource={alerts.data} <AlertList dataSource={alerts.data}
title={formatMessage({id:'alert.overview.alertlist.title'})} title={formatMessage({id:'alert.overview.alertlist.title'})}
legendItems={pickLegendItems(['ACTIVE','ERROR','ACKNOWLEDGED'])} legendItems={pickLegendItems(['ACTIVE','ERROR','ACKNOWLEDGED'])}
@ -159,7 +201,7 @@ export default (props)=>{
onChange: onAlertPageChange, onChange: onAlertPageChange,
}}/> }}/>
</div> </div>
<div style={{marginTop:10}}> {historyAlerts.data.length > 0 && <div className="alertlist-item">
<AlertList dataSource={historyAlerts.data} <AlertList dataSource={historyAlerts.data}
title={formatMessage({id:'alert.overview.alertlist-history.title'})} title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick} onItemClick={onItemClick}
@ -169,47 +211,11 @@ export default (props)=>{
total: historyAlerts.total, total: historyAlerts.total,
onChange: onAlertHistoryPageChange, onChange: onAlertHistoryPageChange,
}}/> }}/>
</div> </div>}
</div> </div>
<div className="right"> <div className="right">
<div style={{height:'150px'}}>
<Chart>
<Settings theme={theme} />
<Axis id="bottom" position={Position.Bottom} title="Last 3 months" showOverlappingTicks tickFormat={timeFormatter(niceTimeFormatByDay(data.metrics.last_tree_month.day))} />
<Axis
id="left"
title="Alert number"
position={Position.Left}
/>
<LineSeries
id="lines"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={data.metrics.last_tree_month?.data || []}
/>
</Chart>
</div>
<div style={{height:'150px', marginTop: 10}}>
<Chart>
<Settings showLegend showLegendExtra legendPosition={Position.Right} theme={theme} />
<Axis id="bottom" position={Position.Bottom} title="Top 10 cluster" showOverlappingTicks />
<Axis id="left2" title="Alert number" position={Position.Left} tickFormat={(d) => Number(d).toFixed(0)} />
<BarSeries
id="bars"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
stackAccessors={['x']}
splitSeriesAccessors={['g']}
data={data.metrics.top_ten_cluster?.data || []}
/>
</Chart>
</div>
</div> </div>
</div> </div>
</Spin> </Spin>

View File

@ -1,9 +1,9 @@
.layout{ .layout{
display: flex; // display: flex;
.left{ .left{
display: flex; // display: flex;
flex: 1 1 60%; // flex: 1 1 60%;
flex-direction: column; // flex-direction: column;
.state-count{ .state-count{
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -21,9 +21,19 @@
font-size: 12px; font-size: 12px;
font-weight: normal; font-weight: normal;
} }
} }
margin-bottom: 10px; margin-bottom: 10px;
} }
.alertlist-item{
margin-top: 20px;
}
}
.charts{
display: flex;
margin-top: 25px;
>.chart{
flex: 1 1 50%;
}
} }
.right{ .right{
flex: 1 1 40%; flex: 1 1 40%;
@ -31,6 +41,6 @@
} }
.overview-wrapper { .overview-wrapper {
padding: 10px; padding: 20px;
background-color: #fff; background-color: #fff;
} }

View File

@ -227,7 +227,7 @@ class Overview extends React.Component {
> >
<Card.Meta title='存储空间' className={styles.title} /> <Card.Meta title='存储空间' className={styles.title} />
<div> <div>
<span className={styles.total}>{totalStoreSize.size}</span><span className={styles.unit}>{totalStoreSize.unit}</span> <span className={styles.total}>{totalStoreSize.size || '-'}</span><span className={styles.unit}>{totalStoreSize.unit}</span>
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@ -31,7 +31,7 @@ import * as styles from './discover.scss';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { connect } from 'dva'; import { connect } from 'dva';
import { Card, Spin } from 'antd'; import { Card, Spin, message } from 'antd';
// import DiscoverGrid from './Components/discover_grid'; // import DiscoverGrid from './Components/discover_grid';
import {flattenHitWrapper} from '../../components/kibana/data/common/index_patterns/index_patterns'; import {flattenHitWrapper} from '../../components/kibana/data/common/index_patterns/index_patterns';
import {getStateColumnActions} from '../../components/kibana/discover/public/application/angular/doc_table/actions/columns'; import {getStateColumnActions} from '../../components/kibana/discover/public/application/angular/doc_table/actions/columns';
@ -56,8 +56,6 @@ const SidebarMemoized = React.memo(DiscoverSidebar);
const {filterManager, queryStringManager, timefilter, storage, getEsQuery, getSearchParams, const {filterManager, queryStringManager, timefilter, storage, getEsQuery, getSearchParams,
intervalOptions, getTimeBuckets, fetchESRequest, services} = getContext(); intervalOptions, getTimeBuckets, fetchESRequest, services} = getContext();
//const histogramData = buildPointSeriesData(chartTable, dimensions);
const SearchBar = createSearchBar(); const SearchBar = createSearchBar();
@ -246,7 +244,7 @@ const Discover = (props)=>{
state, state,
useNewFieldsApi:false, useNewFieldsApi:false,
}), }),
[indexPattern, state] [indexPattern,state]
); );
const collapseIcon = useRef(null); const collapseIcon = useRef(null);
@ -309,6 +307,41 @@ const Discover = (props)=>{
const hits = searchRes.hits.total?.value || searchRes.hits.total; const hits = searchRes.hits.total?.value || searchRes.hits.total;
const resetQuery = ()=>{}; const resetQuery = ()=>{};
const showDatePicker = indexPattern.timeFieldName != ""; const showDatePicker = indexPattern.timeFieldName != "";
const saveDocument = useCallback(async ({_index, _id, _type, _source})=>{
const {http} = getContext();
const res = await http.put(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, {
prependBasePath: false,
query: {
_type,
},
body: JSON.stringify(_source),
});
if(res.error){
message.error(res.error)
return res
}
message.success('saved successfully');
updateQuery()
return res
},[props.selectedCluster])
const deleteDocument = useCallback(async ({_index, _id, _type})=>{
const {http} = getContext();
const res = await http.delete(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, {
prependBasePath: false,
query: {
_type,
}
});
if(res.error){
message.error(res.error)
return res
}
message.success('deleted successfully');
updateQuery()
return res
},[props.selectedCluster])
return ( return (
<Card bordered={false}> <Card bordered={false}>
@ -495,7 +528,9 @@ const Discover = (props)=>{
onFilter={onAddFilter} onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn} onRemoveColumn={onRemoveColumn}
onMoveColumn={onMoveColumn} onMoveColumn={onMoveColumn}
onAddColumn={onAddColumn}
onChangeSortOrder={onSort} onChangeSortOrder={onSort}
document={{saveDocument, deleteDocument}}
hits={rows}/> hits={rows}/>
</div> </div>
):null} ):null}

View File

@ -383,14 +383,10 @@ class EditableCell extends React.Component {
getFieldType = (record, key)=>{ getFieldType = (record, key)=>{
const {doclist} = this.props; const {doclist} = this.props;
// if(!doclist.mappings[record._index]){
// console.log(record, doclist.mappings)
// return
// }
let properties = null; let properties = null;
let _type = record._type || doclist._type; let _type = record._type || doclist._type;
if(typeof _type !== 'undefined' && _type !== '' && _type !== '_doc'){ if(typeof _type !== 'undefined' && _type !== '' && _type !== '_doc'){
properties = doclist.mappings[record._index].mappings[_type].properties; properties = doclist.mappings[record._index].mappings[_type]?.properties || {};
}else{ }else{
properties = doclist.mappings[record._index].mappings.properties; properties = doclist.mappings[record._index].mappings.properties;
} }
@ -565,9 +561,10 @@ class EditableCell extends React.Component {
} }
} }
@connect(({document,cluster})=>({ @connect(({document,global})=>({
document, document,
cluster, clusterID: global.selectedClusterID,
cluster: global.selectedCluster,
})) }))
@Form.create() @Form.create()
class Doucment extends React.Component { class Doucment extends React.Component {
@ -580,10 +577,13 @@ class Doucment extends React.Component {
// } // }
fetchData = (params) => { fetchData = (params) => {
const {dispatch} = this.props; const {dispatch, clusterID} = this.props;
return dispatch({ return dispatch({
type: 'document/fetchDocList', type: 'document/fetchDocList',
payload: params, payload: {
...params,
clusterID
},
}) })
} }
@ -595,38 +595,28 @@ class Doucment extends React.Component {
langDisposer.dispose(); langDisposer.dispose();
} }
} }
componentDidMount(){ componentDidUpdate(oldProps,newState,snapshot){
// initEditor() if(oldProps.clusterID != this.props.clusterID){
const {location, dispatch } = this.props; this.initData()
//console.log(match, location);
let index = location.query.index;
let cluster = location.query.cluster || 'single-es';
if(!cluster){
return
} }
}
initData = ()=>{
const {dispatch, clusterID} = this.props;
dispatch({ dispatch({
type: 'document/fetchMappings', type: 'document/fetchMappings',
payload: { payload: {
cluster, clusterID,
} }
}); });
dispatch({ dispatch({
type: 'document/fetchIndices', type: 'document/fetchIndices',
payload: { payload: {
cluster, clusterID,
} }
}).then(()=>{
if(!index){
return
}
this.fetchData({
pageSize: 10,
pageIndex: 1,
cluster,
index,
})
}) })
}
componentDidMount(){
this.initData()
} }
handleNewClick = ()=>{ handleNewClick = ()=>{
@ -644,7 +634,6 @@ class Doucment extends React.Component {
let _index = indices[0]; let _index = indices[0];
let _type = ''; let _type = '';
if(indices.length > 0){ if(indices.length > 0){
//console.log(this.indexSelEl);
let vals = this.indexSelEl.state.value; let vals = this.indexSelEl.state.value;
if(vals.length === 0){ if(vals.length === 0){
Modal.error({ Modal.error({
@ -701,9 +690,8 @@ class Doucment extends React.Component {
handleSearchClick = (e)=>{ handleSearchClick = (e)=>{
let value = this.keywordEl.state.value; let value = this.keywordEl.state.value;
let index = this.indexEl.state.value; let index = this.indexEl.state.value;
let cluster = this.clusterEl.rcSelect.state.value[0];
let filter = ''; let filter = '';
if(!cluster || !index){ if(!index){
message.error('please select cluster and index'); message.error('please select cluster and index');
return; return;
} }
@ -711,7 +699,6 @@ class Doucment extends React.Component {
filter = this.filterGetter(); filter = this.filterGetter();
} }
this.fetchData({ this.fetchData({
cluster,
index, index,
pageSize: this.props.document.pageSize, pageSize: this.props.document.pageSize,
pageIndex: 1, pageIndex: 1,
@ -719,10 +706,10 @@ class Doucment extends React.Component {
filter, filter,
keyword: value, keyword: value,
}).then(()=>{ }).then(()=>{
if(this.hashChanged){ // if(this.hashChanged){
router.push(`/data/document?cluster=${cluster}&index=${index}`); // router.push(`/data/document?cluster=${cluster}&index=${index}`);
this.hashChanged = !this.hashChanged; // this.hashChanged = !this.hashChanged;
} // }
}) })
} }
@ -741,8 +728,9 @@ class Doucment extends React.Component {
// if((indices && indices.length > 1)){ // if((indices && indices.length > 1)){
// return; // return;
// } // }
const {major} = this.props.cluster; const {version} = this.props.cluster;
if(indices && indices.length >= 0){ const major = version.split('.')?.[0] || '';
if(indices && indices.length >= 0 && major !=''){
indices = getESAPI(major).extractIndicesFromMappings(mappings).filter(item=>{ indices = getESAPI(major).extractIndicesFromMappings(mappings).filter(item=>{
if(indices.length > 0){ if(indices.length > 0){
return indices.indexOf(item.index) > -1; return indices.indexOf(item.index) > -1;
@ -769,11 +757,6 @@ class Doucment extends React.Component {
<div> <div>
{(indices && indices.length>0) ? (<Cascader ref={el=>{this.indexSelEl=el}} onChange={(vals)=>{this.handleResultTabKeyChange(vals[0])}} value={[resultKey]} options={indices} style={{width: 200, marginRight:5}} placeholder="please select a index"> {(indices && indices.length>0) ? (<Cascader ref={el=>{this.indexSelEl=el}} onChange={(vals)=>{this.handleResultTabKeyChange(vals[0])}} value={[resultKey]} options={indices} style={{width: 200, marginRight:5}} placeholder="please select a index">
</Cascader>) : ''} </Cascader>) : ''}
{/*{(indices) ? (<Select ref={el=>{this.indexSelEl=el}} style={{width: 200, marginRight:5}} placeholder="please select a index">*/}
{/* {indices.map(item=>{*/}
{/* return (<Select.Option key={item} label={item}>{item}</Select.Option>)*/}
{/* })}*/}
{/*</Select>) : ''}*/}
<Button type="primary" icon="plus" onClick={this.handleNewClick}>{formatMessage({ id: 'form.button.new' })}</Button> <Button type="primary" icon="plus" onClick={this.handleNewClick}>{formatMessage({ id: 'form.button.new' })}</Button>
<span style={{marginLeft:20}}> <span style={{marginLeft:20}}>
{/*Select Viewer: */} {/*Select Viewer: */}
@ -806,9 +789,7 @@ class Doucment extends React.Component {
value: index, value: index,
}; };
}) })
const clusters = ["single-es"]; let {index, indices, tableMode}= this.props.document;
let {cluster, index, indices, tableMode}= this.props.document;
cluster = cluster || this.props.location.query.cluster || 'single-es';
index = index || this.props.location.query.index; index = index || this.props.location.query.index;
indices = indices || []; indices = indices || [];
@ -824,12 +805,7 @@ class Doucment extends React.Component {
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}> <Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={20} style={{paddingLeft:0}}> <Col span={20} style={{paddingLeft:0}}>
<Input.Group compact> <Input.Group compact>
<Select ref={el=>this.clusterEl=el} defaultValue={cluster} style={{width: '20%'}}> <InputSelect data={clusterIndices} onChange={()=>{this.hashChanged=true;}} defaultValue={index} ref={el=>{this.indexEl=el}} placeholder="input index or index pattern" style={{width: '40%'}}/>
{
clusters.map(op=>(<Select.Option value={op} key={op}>{op}</Select.Option>))
}
</Select>
<InputSelect data={clusterIndices} onChange={()=>{this.hashChanged=true;}} defaultValue={index} ref={el=>{this.indexEl=el}} placeholder="input index or index pattern" style={{width: '25%'}}/>
<Input <Input
style={{width:"40%", display: this.state.bodyDisplay === 'none' ? 'inline': 'none'}} style={{width:"40%", display: this.state.bodyDisplay === 'none' ? 'inline': 'none'}}
ref={el=>this.keywordEl=el} ref={el=>this.keywordEl=el}

View File

@ -17,12 +17,14 @@ import {
Menu, Menu,
Table, Table,
Dropdown, Dropdown,
Icon, Popconfirm Icon, Popconfirm,
Switch,
} from 'antd'; } from 'antd';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
import styles from '../List/TableList.less'; import styles from '../List/TableList.less';
import {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings'; import {transformSettingsForApi} from '@/lib/elasticsearch/edit_settings';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TextArea } = Input; const { TextArea } = Input;
@ -107,8 +109,9 @@ class CreateForm extends React.Component {
/* eslint react/no-multi-comp:0 */ /* eslint react/no-multi-comp:0 */
@connect(({ index }) => ({ @connect(({ index,global }) => ({
index index,
clusterID: global.selectedClusterID,
})) }))
@Form.create() @Form.create()
class Index extends PureComponent { class Index extends PureComponent {
@ -120,6 +123,7 @@ class Index extends PureComponent {
drawerVisible: false, drawerVisible: false,
editingIndex:{}, editingIndex:{},
indexActiveKey: '1', indexActiveKey: '1',
showSystemIndices: false,
}; };
columns = [ columns = [
{ {
@ -137,6 +141,9 @@ class Index extends PureComponent {
{ {
title: '文档数', title: '文档数',
dataIndex: 'docs_count', dataIndex: 'docs_count',
render: (val)=>{
return val || 0;
}
}, },
{ {
title: '主分片数', title: '主分片数',
@ -165,13 +172,18 @@ class Index extends PureComponent {
componentDidMount() { componentDidMount() {
this.fetchData() this.fetchData()
} }
componentDidUpdate(oldProps,newState,snapshot){
if(oldProps.clusterID != this.props.clusterID){
this.fetchData()
}
}
fetchData = ()=>{ fetchData = ()=>{
const { dispatch } = this.props; const { dispatch, clusterID } = this.props;
dispatch({ dispatch({
type: 'index/fetchIndices', type: 'index/fetchIndices',
payload: { payload: {
cluster: 'single-es' clusterID: clusterID,
} }
}); });
} }
@ -185,11 +197,12 @@ class Index extends PureComponent {
}; };
handleDeleteClick = (indexName) => { handleDeleteClick = (indexName) => {
const { dispatch } = this.props; const { dispatch,clusterID } = this.props;
dispatch({ dispatch({
type: 'index/removeIndex', type: 'index/removeIndex',
payload: { payload: {
index: indexName index: indexName,
clusterID,
} }
}); });
}; };
@ -214,12 +227,13 @@ class Index extends PureComponent {
}; };
handleAdd = fields => { handleAdd = fields => {
const { dispatch } = this.props; const { dispatch, clusterID} = this.props;
dispatch({ dispatch({
type: 'index/addIndex', type: 'index/addIndex',
payload: { payload: {
index: fields.index, index: fields.index,
config: JSON.parse(fields.config) config: JSON.parse(fields.config || '{}'),
clusterID
}, },
}); });
this.handleModalVisible(); this.handleModalVisible();
@ -229,7 +243,7 @@ class Index extends PureComponent {
this.setState({ this.setState({
indexActiveKey: activeKey, indexActiveKey: activeKey,
}) })
const {dispatch} = this.props; const {dispatch, clusterID} = this.props;
if(activeKey == '2'){ if(activeKey == '2'){
if(this.props.index.mappings[indexName]){ if(this.props.index.mappings[indexName]){
return return
@ -238,6 +252,7 @@ class Index extends PureComponent {
type: 'index/fetchMappings', type: 'index/fetchMappings',
payload: { payload: {
index: indexName, index: indexName,
clusterID,
} }
}) })
}else if(activeKey == '4'){ }else if(activeKey == '4'){
@ -248,6 +263,7 @@ class Index extends PureComponent {
type: 'index/fetchSettings', type: 'index/fetchSettings',
payload: { payload: {
index: indexName, index: indexName,
clusterID,
} }
}) })
} }
@ -259,12 +275,13 @@ class Index extends PureComponent {
handleIndexSettingsSaveClick = (indexName)=>{ handleIndexSettingsSaveClick = (indexName)=>{
let settings = this.indexSettingsGetter(); let settings = this.indexSettingsGetter();
settings = JSON.parse(settings); settings = JSON.parse(settings);
const {dispatch} = this.props; const {dispatch,clusterID} = this.props;
dispatch({ dispatch({
type: 'index/saveSettings', type: 'index/saveSettings',
payload: { payload: {
index: indexName, index: indexName,
settings: settings, settings: settings,
clusterID,
} }
}) })
} }
@ -281,6 +298,9 @@ class Index extends PureComponent {
} }
indices.push(clusterIndices[key]); indices.push(clusterIndices[key]);
} }
if(!this.state.showSystemIndices){
indices = indices.filter(item=>!item.index.startsWith('.'));
}
const { modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state; const { modalVisible, updateModalVisible, updateFormValues,editingIndex, drawerVisible } = this.state;
const parentMethods = { const parentMethods = {
handleAdd: this.handleAdd, handleAdd: this.handleAdd,
@ -301,7 +321,7 @@ class Index extends PureComponent {
const {form: { getFieldDecorator }} = this.props; const {form: { getFieldDecorator }} = this.props;
return ( return (
<Fragment> <PageHeaderWrapper>
<Card bordered={false}> <Card bordered={false}>
<div className={styles.tableList}> <div className={styles.tableList}>
<div className={styles.tableListForm}> <div className={styles.tableListForm}>
@ -332,7 +352,9 @@ class Index extends PureComponent {
</Form> </Form>
</div> </div>
<div className={styles.tableListOperator}> <div className={styles.tableListOperator}>
<div style={{marginLeft:'auto'}}>显示系统索引<Switch style={{marginLeft:5}}
onChange={(checked)=>{this.setState({showSystemIndices:checked})}}
defaultChecked={this.state.showSystemIndices}/></div>
</div> </div>
<Table bordered <Table bordered
dataSource={indices} dataSource={indices}
@ -357,17 +379,17 @@ class Index extends PureComponent {
> >
<Tabs activeKey={this.state.indexActiveKey} onChange={(activeKey)=>{this.handleIndexTabChanged(activeKey, editingIndex.index)}}> <Tabs activeKey={this.state.indexActiveKey} onChange={(activeKey)=>{this.handleIndexTabChanged(activeKey, editingIndex.index)}}>
<TabPane tab="概览" key="1"> <TabPane tab="概览" key="1">
<Descriptions title="General" column={2}> <Descriptions column={2}>
<Descriptions.Item label="健康">{editingIndex.health}</Descriptions.Item> <Descriptions.Item label="健康">{editingIndex.health}</Descriptions.Item>
<Descriptions.Item label="状态">{editingIndex.status}</Descriptions.Item> <Descriptions.Item label="状态">{editingIndex.status}</Descriptions.Item>
<Descriptions.Item label="主分片数">{editingIndex.shards}</Descriptions.Item> <Descriptions.Item label="主分片数">{editingIndex.shards}</Descriptions.Item>
<Descriptions.Item label="副分片数">{editingIndex.replicas}</Descriptions.Item> <Descriptions.Item label="副分片数">{editingIndex.replicas}</Descriptions.Item>
<Descriptions.Item label="文档数">{editingIndex.docs_count}</Descriptions.Item> <Descriptions.Item label="文档数">{editingIndex.docs_count}</Descriptions.Item>
<Descriptions.Item label="删除文档数">{editingIndex.docs_deleted}</Descriptions.Item> <Descriptions.Item label="删除文档数">{editingIndex.docs_deleted}</Descriptions.Item>
<Descriptions.Item label="存贮大小"></Descriptions.Item> <Descriptions.Item label="存贮大小">{editingIndex.store_size}</Descriptions.Item>
<Descriptions.Item label="主存贮大小"></Descriptions.Item> <Descriptions.Item label="主存贮大小">{editingIndex.pri_store_size}</Descriptions.Item>
<Descriptions.Item label="别名"> {/* <Descriptions.Item label="">
</Descriptions.Item> </Descriptions.Item> */}
</Descriptions> </Descriptions>
</TabPane> </TabPane>
<TabPane tab="Mappings" key="2"> <TabPane tab="Mappings" key="2">
@ -431,7 +453,7 @@ class Index extends PureComponent {
</Dropdown> </Dropdown>
</div> </div>
</Drawer> </Drawer>
</Fragment> </PageHeaderWrapper>
); );
} }
} }

View File

@ -2,7 +2,7 @@ import {AutocompleteService} from '../../components/kibana/data/public/autocompl
import {FilterManager} from '../../components/kibana/data/public/query/filter_manager/filter_manager'; import {FilterManager} from '../../components/kibana/data/public/query/filter_manager/filter_manager';
import {QueryStringManager} from '../../components/kibana/data/public/query/query_string/query_string_manager'; import {QueryStringManager} from '../../components/kibana/data/public/query/query_string/query_string_manager';
import {Timefilter, TimeHistory} from '../../components/kibana/data/public/query/timefilter'; import {Timefilter, TimeHistory} from '../../components/kibana/data/public/query/timefilter';
import { useState, useEffect } from 'react'; import { useState, useEffect, createContext } from 'react';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import {buildEsQuery} from '../../components/kibana/data/common/es_query/es_query/build_es_query'; import {buildEsQuery} from '../../components/kibana/data/common/es_query/es_query/build_es_query';
import {getCalculateAutoTimeExpression} from '../../components/kibana/data/common/search/aggs/utils/calculate_auto_time_expression'; import {getCalculateAutoTimeExpression} from '../../components/kibana/data/common/search/aggs/utils/calculate_auto_time_expression';

View File

@ -200,29 +200,28 @@ export default {
message.success("添加文档成功") message.success("添加文档成功")
}, },
*fetchIndices({payload}, {call, put}){ *fetchIndices({payload}, {call, put}){
let resp = yield call(getIndices) let resp = yield call(getIndices, payload)
if(resp.status === false){ if(resp.error){
message.warn("获取数据失败") message.warn("获取数据失败")
return return
} }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
clusterIndices: resp.payload, clusterIndices: resp,
cluster: payload.cluster,
} }
}) })
}, },
*fetchMappings({payload}, {call, put}){ *fetchMappings({payload}, {call, put}){
let resp = yield call(getMappings, payload); let resp = yield call(getMappings, payload);
if(resp.status === false){ if(resp.error){
message.warn("get mappings failed") message.warn("get mappings failed")
return return
} }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
mappings: resp.payload, mappings: resp,
} }
}) })
} }

View File

@ -11,48 +11,48 @@ export default {
}, },
effects:{ effects:{
*fetchIndices({payload}, {call, put}){ *fetchIndices({payload}, {call, put}){
let resp = yield call(getIndices) let resp = yield call(getIndices, payload)
if(resp.status === false){ if(resp.error){
message.warn("获取数据失败") message.warn("获取数据失败")
return return
} }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
clusterIndices: resp.payload, clusterIndices: resp,
// cluster: payload.cluster, // cluster: payload.cluster,
} }
}) })
}, },
*fetchMappings({payload}, {call, put}){ *fetchMappings({payload}, {call, put}){
let resp = yield call(getMappings, payload); let resp = yield call(getMappings, payload);
if(resp.status === false){ if(resp.error){
message.warn("get mappings failed") message.warn("get mappings failed")
return return
} }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
mappings: resp.payload, mappings: resp,
} }
}) })
}, },
*fetchSettings({payload}, {call, put}){ *fetchSettings({payload}, {call, put}){
let resp = yield call(getSettings, payload); let resp = yield call(getSettings, payload);
if(resp.status === false){ if(resp.error){
message.warn("get settings failed") message.warn("get settings failed")
return return
} }
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
settings: resp.payload, settings: resp,
} }
}) })
}, },
*saveSettings({payload}, {call, put, select}){ *saveSettings({payload}, {call, put, select}){
let resp = yield call(updateSettings, payload); let resp = yield call(updateSettings, payload);
if(resp.status === false){ if(resp.error){
message.warn("save settings failed") message.warn("save settings failed")
return return
} }
@ -67,7 +67,7 @@ export default {
}, },
*removeIndex({payload}, {call, put, select}){ *removeIndex({payload}, {call, put, select}){
let resp = yield call(deleteIndex, payload); let resp = yield call(deleteIndex, payload);
if(resp.status === false){ if(resp.error){
message.warn("get mappings failed") message.warn("get mappings failed")
return return
} }
@ -82,12 +82,15 @@ export default {
}, },
*addIndex({payload}, {call, put, select}){ *addIndex({payload}, {call, put, select}){
let resp = yield call(createIndex, payload); let resp = yield call(createIndex, payload);
if(resp.status === false){ if(resp.error){
message.warn("create index failed") message.warn("create index failed")
return return
} }
yield put({ yield put({
type: 'fetchIndices' type: 'fetchIndices',
payload: {
clusterID: payload.clusterID,
}
}) })
}, },
}, },

View File

@ -7,6 +7,7 @@
button { button {
margin-right: 8px; margin-right: 8px;
} }
display: flex;
} }
} }

View File

@ -11,6 +11,7 @@ import {
message, message,
Divider, Divider,
Table, AutoComplete, Switch, Table, AutoComplete, Switch,
Popconfirm
} from 'antd'; } from 'antd';
import styles from '../../List/TableList.less'; import styles from '../../List/TableList.less';
@ -18,42 +19,6 @@ import styles from '../../List/TableList.less';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TextArea } = Input; const { TextArea } = Input;
const CreateForm = Form.create()(props => {
const { modalVisible, form, handleAdd, handleModalVisible } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
if (err) return;
form.resetFields();
handleAdd(fieldsValue);
});
};
return (
<Modal
destroyOnClose
title="新建索引"
visible={modalVisible}
width={640}
onOk={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="索引设置">
{form.getFieldDecorator('settings', {
rules: [{ required: true }],
})(<TextArea
style={{ minHeight: 24 }}
placeholder="请输入"
rows={9}
/>)}
</FormItem>
</Modal>
);
});
const UpdateForm = Form.create()(props => { const UpdateForm = Form.create()(props => {
const { updateModalVisible, handleUpdateModalVisible, handleUpdate,values,form, indices } = props; const { updateModalVisible, handleUpdateModalVisible, handleUpdate,values,form, indices } = props;
@ -156,23 +121,28 @@ class AliasManage extends PureComponent {
<Fragment> <Fragment>
{/*<a onClick={() => this.handleUpdateModalVisible(true, record)}>别名设置</a>*/} {/*<a onClick={() => this.handleUpdateModalVisible(true, record)}>别名设置</a>*/}
{/*<Divider type="vertical" />*/} {/*<Divider type="vertical" />*/}
<a onClick={() => { <Popconfirm title="确定要删除?" onConfirm={()=>this.handleDeleteAliasClick(record)}> <a>删除</a></Popconfirm>
let indices = [];
for(let index of record.indexes){
indices.push(index.index);
}
let vals = {
alias: record.alias,
indices,
};
this.handleDeleteClick(vals);
}}>删除</a>
</Fragment> </Fragment>
), ),
}, },
]; ];
handleDeleteAliasClick = (record)=>{
let indices = [];
for(let index of record.indexes){
indices.push(index.index);
}
let vals = {
alias: record.alias,
indices,
};
this.handleDeleteClick(vals);
}
componentDidMount() { componentDidMount() {
this.fetchAliasList();
this.fetchIndices();
}
fetchAliasList = ()=>{
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: 'alias/fetchAliasList', type: 'alias/fetchAliasList',
@ -181,6 +151,21 @@ class AliasManage extends PureComponent {
} }
}); });
} }
fetchIndices = ()=>{
const { dispatch } = this.props;
dispatch({
type: 'alias/fetchIndices',
payload: {
clusterID: this.props.selectedClusterID,
}
});
}
componentDidUpdate(oldProps,newState,snapshot){
if(oldProps.selectedClusterID != this.props.selectedClusterID){
this.fetchAliasList();
this.fetchIndices();
}
}
handleStandardTableChange = (pagination, filtersArg, sorter) => { handleStandardTableChange = (pagination, filtersArg, sorter) => {
@ -320,6 +305,7 @@ class AliasManage extends PureComponent {
if(this.state.keyword) { if(this.state.keyword) {
aliasList = aliasList.filter(al=>al.alias.includes(this.state.keyword)) aliasList = aliasList.filter(al=>al.alias.includes(this.state.keyword))
} }
const {indices} = this.props.alias;
return ( return (
<Fragment> <Fragment>
<Card bordered={false}> <Card bordered={false}>
@ -329,11 +315,6 @@ class AliasManage extends PureComponent {
<Button icon="plus" type="primary" onClick={() => this.handleUpdateModalVisible(true)}> <Button icon="plus" type="primary" onClick={() => this.handleUpdateModalVisible(true)}>
新建 新建
</Button> </Button>
{selectedRows.length > 0 && (
<span>
<Button onClick={() => this.handleDeleteClick()}>删除</Button>
</span>
)}
</div> </div>
<Table <Table
size="small" size="small"
@ -345,8 +326,8 @@ class AliasManage extends PureComponent {
return ( return (
<div> <div>
<AliasIndexTable rawData={record} <AliasIndexTable rawData={record}
handleDeleteClick={this.handleDeleteClick} handleDeleteClick={this.handleDeleteClick}
handleUpdateModalVisible={this.handleUpdateModalVisible} data={record.indexes}/> handleUpdateModalVisible={this.handleUpdateModalVisible} data={record.indexes}/>
</div> </div>
); );
}} }}
@ -360,7 +341,7 @@ class AliasManage extends PureComponent {
{...updateMethods} {...updateMethods}
updateModalVisible={updateModalVisible} updateModalVisible={updateModalVisible}
values={updateFormValues} values={updateFormValues}
indices={['test-custom', 'dict']} indices={indices||[]}
/> />
</Fragment> </Fragment>
@ -401,12 +382,13 @@ class AliasIndexTable extends React.Component {
alias: this.props.rawData.alias, alias: this.props.rawData.alias,
})}>设置</a> })}>设置</a>
<Divider type="vertical" /> <Divider type="vertical" />
<a onClick={() => { <Popconfirm title="确定要删除?" onConfirm={() => {
this.props.handleDeleteClick({ this.props.handleDeleteClick({
...record, ...record,
alias: this.props.rawData.alias, alias: this.props.rawData.alias,
}); });
}}>删除</a> }}><a>删除</a>
</Popconfirm>
</div> </div>
), ),
},] },]
@ -429,7 +411,7 @@ class IndexComplete extends React.Component {
} }
} }
handleSearch = v => { handleSearch = v => {
let data = this.props.dataSource.filter(d=>d.includes(v)); let data = this.props.dataSource.filter(d=>d.includes(v.replace(/\*$/, '')));
// if(data.length > 0 && v.length >0) { // if(data.length > 0 && v.length >0) {
// data.push(v+'*'); // data.push(v+'*');
// } // }

View File

@ -1,4 +1,5 @@
import {getAliasList, doAlias } from '@/services/alias'; import {getAliasList, doAlias } from '@/services/alias';
import {getIndices } from '@/services/indices';
export default { export default {
namespace: 'alias', namespace: 'alias',
@ -19,6 +20,19 @@ export default {
} }
}) })
}, },
*fetchIndices({ payload }, { call, put }) {
const res = yield call(getIndices, payload);
let indices = [];
for(let k in res){
indices.push(k);
}
yield put({
type: 'saveData',
payload: {
indices,
}
})
},
*add({ payload, callback }, { call, put }) { *add({ payload, callback }, { call, put }) {
}, },

View File

@ -69,7 +69,6 @@ export default {
return return
} }
resp.payload = formatESSearchResult(resp.payload); resp.payload = formatESSearchResult(resp.payload);
console.log(resp.payload);
resp.payload.data = resp.payload.data.map((item)=>{ resp.payload.data = resp.payload.data.map((item)=>{
item.content = utf8.decode(atob(item.content)) item.content = utf8.decode(atob(item.content))
return item; return item;

View File

@ -17,7 +17,6 @@ export default {
*fetchList({ payload }, { call, put , select }) { *fetchList({ payload }, { call, put , select }) {
payload.cluster_id = yield select(state => state.global.selectedClusterID); payload.cluster_id = yield select(state => state.global.selectedClusterID);
const res = yield call(getTemplateList, payload); const res = yield call(getTemplateList, payload);
console.log("fetchList response:",res);
if (res.hits) { if (res.hits) {
let newList = []; let newList = [];
let hits = res.hits.hits || []; let hits = res.hits.hits || [];

View File

@ -12,4 +12,6 @@ export function buildQueryArgs(params){
argsStr = argsStr.slice(0, argsStr.length -1) argsStr = argsStr.slice(0, argsStr.length -1)
} }
return argsStr; return argsStr;
} }
export const ESPrefix = '/elasticsearch';

View File

@ -1,12 +1,12 @@
import request from '@/utils/request'; import request from '@/utils/request';
import {pathPrefix} from './common'; import {pathPrefix, ESPrefix} from './common';
export async function getDocList(params) { export async function getDocList(params) {
params.from = (params.pageIndex - 1) * params.pageSize; params.from = (params.pageIndex - 1) * params.pageSize;
params.size = params.pageSize; params.size = params.pageSize;
delete params.pageSize; delete params.pageSize;
delete params.pageIndex; delete params.pageIndex;
return request(`${pathPrefix}/doc/${params.index}/_search`, { return request(`${ESPrefix}/${params.clusterID}/doc/${params.index}/_search`, {
method: 'POST', method: 'POST',
body: params, body: params,
}); });

View File

@ -1,43 +1,43 @@
import request from '@/utils/request'; import request from '@/utils/request';
import {pathPrefix} from './common'; import {pathPrefix, ESPrefix} from './common';
export async function getMappings(payload){ export async function getMappings(params){
let index = payload.index || '*' let index = params.index || '*'
let url = `${pathPrefix}/index/${index}/_mappings`; let url = `${ESPrefix}/${params.clusterID}/index/${index}/_mappings`;
return request(url,{ return request(url,{
method: 'GET', method: 'GET',
expirys: 0, expirys: 0,
}); });
} }
export async function getSettings(payload){ export async function getSettings(params){
let index = payload.index || '*' let index = params.index || '*'
let url = `${pathPrefix}/index/${index}/_settings`; let url = `${ESPrefix}/${params.clusterID}/index/${index}/_settings`;
return request(url,{ return request(url,{
method: 'GET', method: 'GET',
expirys: 0, expirys: 0,
}); });
} }
export async function updateSettings(payload){ export async function updateSettings(params){
let index = payload.index let index = params.index
let url = `${pathPrefix}/index/${index}/_settings`; let url = `${ESPrefix}/${params.clusterID}/index/${index}/_settings`;
return request(url,{ return request(url,{
method: 'PUT', method: 'PUT',
body: payload.settings, body: params.settings,
expirys: 0, expirys: 0,
}); });
} }
export async function getIndices(params) { export async function getIndices(params) {
return request(`${pathPrefix}/_cat/indices`, { return request(`${ESPrefix}/${params.clusterID}/_cat/indices`, {
method: 'GET' method: 'GET'
}); });
} }
export async function deleteIndex(params) { export async function deleteIndex(params) {
let index = params.index; let index = params.index;
return request(`${pathPrefix}/index/${index}`, { return request(`${ESPrefix}/${params.clusterID}/index/${index}`, {
method: 'DELETE' method: 'DELETE'
}); });
} }
@ -45,7 +45,7 @@ export async function deleteIndex(params) {
export async function createIndex(params) { export async function createIndex(params) {
let index = params.index; let index = params.index;
return request(`${pathPrefix}/index/${index}`, { return request(`${ESPrefix}/${params.clusterID}/index/${index}`, {
method: 'POST', method: 'POST',
body: params.config body: params.config
}); });

View File

@ -2,9 +2,8 @@ import request from '@/utils/request';
export async function getTemplateList(payload){ export async function getTemplateList(payload){
let url = `/elasticsearch/${payload.cluster_id}/search_template/_search?from=${payload.from}&size=${payload.size}`; let url = `/elasticsearch/${payload.cluster_id}/search_template?from=${payload.from}&size=${payload.size}`;
payload.name && (url+= `&name=${payload.name}`); payload.name && (url+= `&name=${payload.name}`);
console.log("url:",url);
return request(url,{ return request(url,{
method: 'GET', method: 'GET',
// body: payload, // body: payload,