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) {
client := elastic.GetClient(handler.Config.Elasticsearch)
targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
reqBody := map[string]interface{}{}
resResult := newResponseBody()
resBody := newResponseBody()
err := handler.DecodeJSON(req, &reqBody)
if err != nil {
resResult["status"] = false
resResult["error"] = err.Error()
handler.WriteJSON(w, resResult, http.StatusOK)
resBody["error"] = err.Error()
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
indexName := ps.ByName("index")
id := ps.ByName("id")
if strings.Trim(id, "/") == "" {
id = util.GetUUID()
docID := ps.ByName("docId")
if strings.Trim(docID, "/") == "" {
docID = util.GetUUID()
}
docType := handler.GetParameter(req, "_type")
_, err = client.Index(indexName, docType, id, reqBody)
insertRes, err := client.Index(indexName, docType, docID, reqBody)
if err != nil {
resResult["status"] = false
resResult["error"] = err
handler.WriteJSON(w, resResult, http.StatusOK)
resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
reqBody["id"] = id
resResult["payload"] = reqBody
handler.WriteJSON(w, resResult, http.StatusOK)
reqBody["_id"] = docID
resBody["result"] = insertRes.Result
handler.WriteJSON(w, resBody, http.StatusOK)
}
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{}{}
resResult := newResponseBody()
err := handler.DecodeJSON(req, &reqBody)
if err != nil {
resResult["status"] = false
resResult["error"] = err
handler.WriteJSON(w, resResult, http.StatusOK)
resBody["error"] = err
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
indexName := ps.ByName("index")
id := ps.ByName("id")
docID := ps.ByName("docId")
typ := handler.GetParameter(req, "_type")
resp, err := client.Get(indexName,typ, id)
insertRes, err := client.Index(indexName, typ, docID, reqBody)
if err != nil {
resResult["status"] = false
resResult["error"] = err.Error()
handler.WriteJSON(w, resResult, http.StatusOK)
resBody["error"] = err.Error()
handler.WriteJSON(w, resBody, http.StatusOK)
return
}
source := resp.Source
for k, v := range reqBody {
if k == "id" {
continue
}
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)
resBody["_source"] = reqBody
resBody["_id"] = docID
resBody["result"] = insertRes.Result
handler.WriteJSON(w, resBody, http.StatusOK)
}
func (handler APIHandler) HandleDeleteDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
client := elastic.GetClient(handler.Config.Elasticsearch)
resResult := newResponseBody()
indexName := ps.ByName("index")
id := ps.ByName("id")
typ := handler.GetParameter(req, "_type")
_, err := client.Delete(indexName, typ, id)
if err != nil {
resResult["error"] = err.Error()
resResult["status"] = false
handler.WriteJSON(w, resResult, http.StatusOK)
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
}
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) {
client := elastic.GetClient(handler.Config.Elasticsearch)
targetClusterID := ps.ByName("id")
client := elastic.GetClient(targetClusterID)
reqBody := docReqBody{}
resResult := newResponseBody()
err := handler.DecodeJSON(req, &reqBody)

View File

@ -2,13 +2,13 @@ package index_management
import (
"fmt"
log "github.com/cihub/seelog"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"infini.sh/framework/modules/elastic/common"
"net/http"
log "github.com/cihub/seelog"
)
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
})
resBody := util.MapStr{
"total_node": totalNode,
"total_store_size_in_bytes": totalStoreSize,

View File

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

View File

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

View File

@ -139,25 +139,6 @@ export default [
name: 'data',
icon: 'database',
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',
// name: 'overview',
@ -165,14 +146,16 @@ export default [
// routes:[
// { path: '/', redirect: '/' },
// ],
// }, {
// 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',
// name: 'document',
// component: './DataManagement/Document',
@ -269,9 +252,9 @@ export default [
// {
// path: '/search/alias',
// redirect: '/search/alias/index',
// routes:[
// { path: '/', redirect: '/' },
// ],
// // routes:[
// // { path: '/', redirect: '/' },
// // ],
// },
// {
// path: '/search/alias/index',
@ -296,9 +279,9 @@ export default [
// {
// path: '/search/dict',
// redirect: '/search/dict/professional',
// routes:[
// { path: '/', redirect: '/' },
// ],
// // routes:[
// // { path: '/', redirect: '/' },
// // ],
// },
// {
// path: '/search/dict/professional',

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,14 @@
import {EuiIcon} from '@elastic/eui';
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 {
columns: string[];
@ -8,6 +17,7 @@ interface Props {
onFilter: (field: any, values: any, operation: any) => void;
onAddColumn?: (name: string) => void;
onRemoveColumn?: (name: string) => void;
document: any;
}
export function Detail({
@ -17,7 +27,59 @@ export function Detail({
onFilter,
onAddColumn,
onRemoveColumn,
document,
}: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 (
<td colSpan={ columns.length + 2 }>
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
@ -34,9 +96,49 @@ export function Detail({
</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">
<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">
{/* <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
className="euiLink"
>View surrounding documents</a>
@ -45,9 +147,9 @@ export function Detail({
<a
className="euiLink"
>View single document</a>
</div>
</div> */}
</div>
</div> */}
</div>
</div>
<div data-test-subj="docViewer">
<DocViewer
@ -62,5 +164,29 @@ export function Detail({
</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;
onRemoveColumn?: (name: string) => void;
row: any;
document: any;
}
export function TableRow({
@ -24,6 +25,7 @@ export function TableRow({
onAddColumn,
onRemoveColumn,
row,
document
}:Props){
const mapping = indexPattern.fields.getByName;
const [open,setOpen] = useState(false);
@ -60,6 +62,7 @@ export function TableRow({
<Detail columns={columns}
indexPattern={indexPattern}
row={row}
document={document}
onFilter={onFilter}
onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,19 +3,22 @@
padding: 10px 5px;
.header{
display: flex;
.title{
color: #333;
font-weight:600;
padding-bottom: 6px;
.total{
color: #999;
margin-left: 15px;
font-size: 12px;
}
}
.legend{
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 (
<div className="alert-overview">
<div className="left">
<AlertList dataSource={alerts.data as any}
title={formatMessage({id:'alert.overview.alertlist.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: alerts.total,
onChange: onAlertPageChange,
}}/>
<div>
<AlertList dataSource={alerts.data}
title={formatMessage({id:'alert.overview.alertlist.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: alerts.total,
onChange: onAlertPageChange,
}}/>
</div>
<div style={{marginTop:10}}>
<AlertList dataSource={historyAlerts.data}
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: historyAlerts.total,
onChange: onAlertHistoryPageChange,
}}/>
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: historyAlerts.total,
onChange: onAlertHistoryPageChange,
}}/>
</div>
</div>
{/* <div className="right">
<div></div>

View File

@ -1,5 +1,5 @@
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 './overview.scss';
import {
@ -148,7 +148,49 @@ export default (props)=>{
</div>
</Card>
</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}
title={formatMessage({id:'alert.overview.alertlist.title'})}
legendItems={pickLegendItems(['ACTIVE','ERROR','ACKNOWLEDGED'])}
@ -159,7 +201,7 @@ export default (props)=>{
onChange: onAlertPageChange,
}}/>
</div>
<div style={{marginTop:10}}>
{historyAlerts.data.length > 0 && <div className="alertlist-item">
<AlertList dataSource={historyAlerts.data}
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick}
@ -169,47 +211,11 @@ export default (props)=>{
total: historyAlerts.total,
onChange: onAlertHistoryPageChange,
}}/>
</div>
</div>}
</div>
<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>
</Spin>

View File

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

View File

@ -227,7 +227,7 @@ class Overview extends React.Component {
>
<Card.Meta title='存储空间' className={styles.title} />
<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>
</Card>
</Col>

View File

@ -31,7 +31,7 @@ import * as styles from './discover.scss';
import { Subscription } from 'rxjs';
import { connect } from 'dva';
import { Card, Spin } from 'antd';
import { Card, Spin, message } from 'antd';
// import DiscoverGrid from './Components/discover_grid';
import {flattenHitWrapper} from '../../components/kibana/data/common/index_patterns/index_patterns';
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,
intervalOptions, getTimeBuckets, fetchESRequest, services} = getContext();
//const histogramData = buildPointSeriesData(chartTable, dimensions);
const SearchBar = createSearchBar();
@ -246,7 +244,7 @@ const Discover = (props)=>{
state,
useNewFieldsApi:false,
}),
[indexPattern, state]
[indexPattern,state]
);
const collapseIcon = useRef(null);
@ -309,6 +307,41 @@ const Discover = (props)=>{
const hits = searchRes.hits.total?.value || searchRes.hits.total;
const resetQuery = ()=>{};
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 (
<Card bordered={false}>
@ -495,7 +528,9 @@ const Discover = (props)=>{
onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn}
onMoveColumn={onMoveColumn}
onAddColumn={onAddColumn}
onChangeSortOrder={onSort}
document={{saveDocument, deleteDocument}}
hits={rows}/>
</div>
):null}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import {
message,
Divider,
Table, AutoComplete, Switch,
Popconfirm
} from 'antd';
import styles from '../../List/TableList.less';
@ -18,42 +19,6 @@ import styles from '../../List/TableList.less';
const FormItem = Form.Item;
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 { updateModalVisible, handleUpdateModalVisible, handleUpdate,values,form, indices } = props;
@ -156,23 +121,28 @@ class AliasManage extends PureComponent {
<Fragment>
{/*<a onClick={() => this.handleUpdateModalVisible(true, record)}>别名设置</a>*/}
{/*<Divider type="vertical" />*/}
<a onClick={() => {
let indices = [];
for(let index of record.indexes){
indices.push(index.index);
}
let vals = {
alias: record.alias,
indices,
};
this.handleDeleteClick(vals);
}}>删除</a>
<Popconfirm title="确定要删除?" onConfirm={()=>this.handleDeleteAliasClick(record)}> <a>删除</a></Popconfirm>
</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() {
this.fetchAliasList();
this.fetchIndices();
}
fetchAliasList = ()=>{
const { dispatch } = this.props;
dispatch({
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) => {
@ -320,6 +305,7 @@ class AliasManage extends PureComponent {
if(this.state.keyword) {
aliasList = aliasList.filter(al=>al.alias.includes(this.state.keyword))
}
const {indices} = this.props.alias;
return (
<Fragment>
<Card bordered={false}>
@ -329,11 +315,6 @@ class AliasManage extends PureComponent {
<Button icon="plus" type="primary" onClick={() => this.handleUpdateModalVisible(true)}>
新建
</Button>
{selectedRows.length > 0 && (
<span>
<Button onClick={() => this.handleDeleteClick()}>删除</Button>
</span>
)}
</div>
<Table
size="small"
@ -345,8 +326,8 @@ class AliasManage extends PureComponent {
return (
<div>
<AliasIndexTable rawData={record}
handleDeleteClick={this.handleDeleteClick}
handleUpdateModalVisible={this.handleUpdateModalVisible} data={record.indexes}/>
handleDeleteClick={this.handleDeleteClick}
handleUpdateModalVisible={this.handleUpdateModalVisible} data={record.indexes}/>
</div>
);
}}
@ -360,7 +341,7 @@ class AliasManage extends PureComponent {
{...updateMethods}
updateModalVisible={updateModalVisible}
values={updateFormValues}
indices={['test-custom', 'dict']}
indices={indices||[]}
/>
</Fragment>
@ -401,12 +382,13 @@ class AliasIndexTable extends React.Component {
alias: this.props.rawData.alias,
})}>设置</a>
<Divider type="vertical" />
<a onClick={() => {
<Popconfirm title="确定要删除?" onConfirm={() => {
this.props.handleDeleteClick({
...record,
alias: this.props.rawData.alias,
});
}}>删除</a>
}}><a>删除</a>
</Popconfirm>
</div>
),
},]
@ -429,7 +411,7 @@ class IndexComplete extends React.Component {
}
}
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) {
// data.push(v+'*');
// }

View File

@ -1,4 +1,5 @@
import {getAliasList, doAlias } from '@/services/alias';
import {getIndices } from '@/services/indices';
export default {
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 }) {
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,8 @@ import request from '@/utils/request';
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}`);
console.log("url:",url);
return request(url,{
method: 'GET',
// body: payload,