console of adding muti tab
This commit is contained in:
parent
b8fb6a3d87
commit
5fcaed73ff
|
@ -0,0 +1,71 @@
|
|||
package index_management
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
httprouter "infini.sh/framework/core/api/router"
|
||||
"infini.sh/framework/core/elastic"
|
||||
"infini.sh/framework/core/orm"
|
||||
"infini.sh/framework/core/util"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (h *APIHandler) HandleSaveCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
resBody := map[string]interface{}{
|
||||
}
|
||||
|
||||
reqParams := elastic.CommonCommand{}
|
||||
err := h.DecodeJSON(req, &reqParams)
|
||||
if err != nil {
|
||||
resBody["error"] = err
|
||||
h.WriteJSON(w, resBody, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
reqParams.Created = time.Now()
|
||||
reqParams.ID = util.GetUUID()
|
||||
esClient := elastic.GetClient(h.Config.Elasticsearch)
|
||||
|
||||
queryDSL :=[]byte(fmt.Sprintf(`{"size":1, "query":{"bool":{"must":{"match":{"title":"%s"}}}}}`, reqParams.Title))
|
||||
var indexName = orm.GetIndexName(reqParams)
|
||||
searchRes, err := esClient.SearchWithRawQueryDSL(indexName, queryDSL)
|
||||
if err != nil {
|
||||
resBody["error"] = err
|
||||
h.WriteJSON(w, resBody, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(searchRes.Hits.Hits) > 0 {
|
||||
resBody["error"] = "title already exists"
|
||||
h.WriteJSON(w, resBody, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = esClient.Index(indexName,"", reqParams.ID, reqParams)
|
||||
|
||||
resBody["_id"] = reqParams.ID
|
||||
resBody["result"] = "created"
|
||||
resBody["_source"] = reqParams
|
||||
|
||||
h.WriteJSON(w, resBody,http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *APIHandler) HandleQueryCommonCommandAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
resBody := map[string]interface{}{
|
||||
}
|
||||
|
||||
//title := h.GetParameterOrDefault(req, "title", "")
|
||||
//tag := h.GetParameterOrDefault(req, "search", "")
|
||||
|
||||
esClient := elastic.GetClient(h.Config.Elasticsearch)
|
||||
//queryDSL :=[]byte(fmt.Sprintf(`{"query":{"bool":{"must":{"match":{"title":"%s"}}}}}`, title))
|
||||
|
||||
searchRes, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(elastic.CommonCommand{}),nil)
|
||||
if err != nil {
|
||||
resBody["error"] = err
|
||||
h.WriteJSON(w, resBody, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
h.WriteJSON(w, searchRes,http.StatusOK)
|
||||
}
|
|
@ -42,6 +42,9 @@ func Init(cfg *config.AppConfig) {
|
|||
ui.HandleUIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), handler.HandleDeleteIndexAction)
|
||||
ui.HandleUIMethod(api.POST, path.Join(esPrefix, "index/:index"), handler.HandleCreateIndexAction)
|
||||
|
||||
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleSaveCommonCommandAction)
|
||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction)
|
||||
|
||||
//new api
|
||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)
|
||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview/alerts"), alerting.GetAlerts)
|
||||
|
@ -74,6 +77,7 @@ func Init(cfg *config.AppConfig) {
|
|||
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/alerts", alerting.GetAlerts)
|
||||
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_monitors/:monitorID/_acknowledge/alerts", alerting.AcknowledgeAlerts)
|
||||
|
||||
|
||||
task.RegisterScheduleTask(task.ScheduleTask{
|
||||
Description: "sync reindex task result",
|
||||
Task: func() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package config
|
||||
|
||||
const LastCommitLog = "N/A"
|
||||
const BuildDate = "N/A"
|
||||
const LastCommitLog = "b8fb6a3, Fri Oct 15 11:41:38 2021 +0800, liugq, console tab v0.1 "
|
||||
const BuildDate = "2021-10-21 13:55:53"
|
||||
|
||||
const EOLDate = "N/A"
|
||||
const EOLDate = "2021-12-31 10:10:10"
|
||||
|
||||
const Version = "0.0.1-SNAPSHOT"
|
||||
const Version = "1.0.0_SNAPSHOT"
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
.fullscreen{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
transform: translate3d(0, 100%, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.slideInUp {
|
||||
animation-name: slideInUp;
|
||||
animation-duration: .2s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
}
|
|
@ -7,8 +7,12 @@ import NoticeIcon from '../NoticeIcon';
|
|||
import HeaderSearch from '../HeaderSearch';
|
||||
import SelectLang from '../SelectLang';
|
||||
import styles from './index.less';
|
||||
import {ConsoleUI} from '@/pages/DevTool/Console';
|
||||
import { Resizable } from "re-resizable";
|
||||
import {ResizeBar} from '@/components/infini/resize_bar';
|
||||
|
||||
export default class GlobalHeaderRight extends PureComponent {
|
||||
state={consoleVisible: false}
|
||||
getNoticeData() {
|
||||
const { notices = [] } = this.props;
|
||||
if (notices.length === 0) {
|
||||
|
@ -95,7 +99,10 @@ export default class GlobalHeaderRight extends PureComponent {
|
|||
|
||||
<a className={styles.action} onClick={()=>{
|
||||
const {history, selectedCluster} = this.props;
|
||||
history.push(`/dev_tool/elasticsearch/${selectedCluster.id}/`);
|
||||
// history.push(`/dev_tool`);
|
||||
this.setState({
|
||||
consoleVisible: !this.state.consoleVisible
|
||||
})
|
||||
}}> <Icon type="code" /></a>
|
||||
|
||||
{/* <NoticeIcon
|
||||
|
@ -151,7 +158,52 @@ export default class GlobalHeaderRight extends PureComponent {
|
|||
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
|
||||
)} */}
|
||||
<SelectLang className={styles.action} />
|
||||
<div style={{
|
||||
display: this.state.consoleVisible ? 'block': 'none',
|
||||
borderTop: "solid 1px #ddd",
|
||||
background: "#fff",
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 1002,
|
||||
}}>
|
||||
{/* <Resizable
|
||||
defaultSize={{
|
||||
height: '50vh'
|
||||
}}
|
||||
minHeight={200}
|
||||
maxHeight="100vh"
|
||||
handleComponent={{ top: <ResizeBar/> }}
|
||||
enable={{
|
||||
top: true,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}> */}
|
||||
<ConsoleUI selectedCluster={this.props.selectedCluster}
|
||||
clusterList={this.props.clusterList}
|
||||
visible={false}
|
||||
minimize={true}
|
||||
onMinimizeClick={()=>{
|
||||
this.setState({
|
||||
consoleVisible: false,
|
||||
})
|
||||
}}
|
||||
clusterStatus={this.props.clusterStatus}
|
||||
resizeable={true}
|
||||
/>
|
||||
{/* </Resizable> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const TopHandle = () => {
|
||||
return <div style={{ background: "red" }}>hello world</div>;
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
export type ClusterHealthStatus = 'green' | 'yellow' | 'red';
|
||||
import {Icon} from 'antd';
|
||||
export type ClusterHealthStatus = 'green' | 'yellow' | 'red' | 'unavailable';
|
||||
|
||||
const statusColorMap: Record<string, string> = {
|
||||
'green': '#39b362',
|
||||
|
@ -15,6 +16,9 @@ interface props {
|
|||
}
|
||||
|
||||
export const HealthStatusCircle = ({status}: props)=>{
|
||||
if(status == 'unavailable'){
|
||||
return <Icon type="close-circle" style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}}/>
|
||||
}
|
||||
const color = convertStatusToColor(status);
|
||||
return <div style={{background: color, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #999', display: 'inline-block'}}></div>
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import './resize_bar.scss';
|
||||
|
||||
export const ResizeBar = () => {
|
||||
return <div className="resize-bar">
|
||||
<div>
|
||||
<div className="dash"></div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
.resize-bar{
|
||||
display: flex;
|
||||
height: 10px;
|
||||
background: #eee;
|
||||
border-top: 1px solid #bbb;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.dash{
|
||||
height: 2px;
|
||||
width: 24px;
|
||||
background: #999;
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ const CommonCommandModal = Form.create()((props: ICommonCommandModalProps) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Modal title="保存常用命令" visible={true} onCancel={props.onClose} onOk={handleConfirm} cancelText="取消" okText="确认">
|
||||
<Modal title="保存常用命令" visible={true} onCancel={props.onClose} onOk={handleConfirm} zIndex={1003} cancelText="取消" okText="确认">
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="标题">
|
||||
{form.getFieldDecorator('title', {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-ignore
|
||||
import React, { useRef, useMemo,useEffect } from 'react';
|
||||
import React, { useRef, useMemo,useEffect, useLayoutEffect } from 'react';
|
||||
import ConsoleInput from './ConsoleInput';
|
||||
import ConsoleOutput from './ConsoleOutput';
|
||||
import { Panel } from './Panel';
|
||||
|
@ -14,12 +14,14 @@ import { createHistory, History, createStorage, createSettings } from '../servic
|
|||
import { create } from '../storage/local_storage_object_client';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import {RequestStatusBar} from './request_status_bar';
|
||||
import useEventListener from '@/lib/hooks/use_event_listener';
|
||||
|
||||
interface props {
|
||||
selectedCluster: any;
|
||||
saveEditorContent: (content: string)=>void;
|
||||
initialText: string;
|
||||
paneKey: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const INITIAL_PANEL_WIDTH = 50;
|
||||
|
@ -30,8 +32,8 @@ const ConsoleWrapper = ({
|
|||
saveEditorContent,
|
||||
initialText,
|
||||
paneKey,
|
||||
height,
|
||||
}:props) => {
|
||||
|
||||
const {
|
||||
requestInFlight: requestInProgress,
|
||||
lastResult: { data: requestData, error: requestError },
|
||||
|
@ -53,26 +55,36 @@ const ConsoleWrapper = ({
|
|||
const statusBarRef = useRef<HTMLDivElement>(null);
|
||||
const consoleRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(()=>{
|
||||
statusBarRef.current && consoleRef.current && (statusBarRef.current.style.width=consoleRef.current.offsetWidth+'px');
|
||||
const winScroll = ()=>{
|
||||
const wsTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
||||
if(wsTop>getElementTop(consoleRef.current)) {
|
||||
statusBarRef.current && (statusBarRef.current.style.position='relative');
|
||||
}else{
|
||||
statusBarRef.current && (statusBarRef.current.style.position='fixed');
|
||||
}
|
||||
}
|
||||
window.addEventListener('scroll', winScroll)
|
||||
return ()=>{
|
||||
window.removeEventListener('scroll', winScroll)
|
||||
}
|
||||
},[])
|
||||
// useEffect(()=>{
|
||||
// const winScroll = ()=>{
|
||||
// const wsTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
||||
// if(wsTop>getElementTop(consoleRef.current)) {
|
||||
// statusBarRef.current && (statusBarRef.current.style.position='relative');
|
||||
// }else{
|
||||
// statusBarRef.current && (statusBarRef.current.style.position='fixed');
|
||||
// }
|
||||
// }
|
||||
// window.addEventListener('scroll', winScroll, {passive:true})
|
||||
// return ()=>{
|
||||
// window.removeEventListener('scroll', winScroll)
|
||||
// }
|
||||
// },[])
|
||||
|
||||
useEventListener('resize', ()=>{
|
||||
statusBarRef.current && consoleRef.current && (statusBarRef.current.style.width=consoleRef.current.offsetWidth+'px');
|
||||
})
|
||||
|
||||
useLayoutEffect(()=>{
|
||||
// console.log(consoleRef.current?.offsetWidth)
|
||||
if(consoleRef.current.offsetWidth>0)
|
||||
statusBarRef.current && consoleRef.current && (statusBarRef.current.style.width=consoleRef.current.offsetWidth+'px');
|
||||
}, [consoleRef.current?.offsetWidth])
|
||||
|
||||
const calcHeight = height > 0 ? (height-35)+'px' : '100%';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={consoleRef} className="Console">
|
||||
<div style={{height: calcHeight}}>
|
||||
<div ref={consoleRef} className="Console" style={{height:'100%'}}>
|
||||
<PanelsContainer resizerClassName="resizer">
|
||||
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }} initialWidth={INITIAL_PANEL_WIDTH}>
|
||||
<ConsoleInput clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} />
|
||||
|
@ -82,12 +94,8 @@ const ConsoleWrapper = ({
|
|||
</Panel>
|
||||
</PanelsContainer>
|
||||
</div>
|
||||
<div ref={statusBarRef} style={{ position:'fixed', bottom:0, borderTop: '1px solid #eee', zIndex:5000}}>
|
||||
<EuiFlexGroup className="consoleContainer"
|
||||
style={{height:30, background:'#fff'}}
|
||||
gutterSize="none"
|
||||
direction="column">
|
||||
<EuiFlexItem className="conApp__tabsExtension">
|
||||
<div ref={statusBarRef} style={{ position:'fixed', bottom:0, borderTop: '1px solid #eee', zIndex:1001, width:'100%'}}>
|
||||
<div style={{background:'#fff',height:30, width:'100%'}}>
|
||||
<RequestStatusBar
|
||||
requestInProgress={requestInProgress}
|
||||
selectedCluster={selectedCluster}
|
||||
|
@ -99,18 +107,20 @@ const ConsoleWrapper = ({
|
|||
statusCode: lastDatum.response.statusCode,
|
||||
statusText: lastDatum.response.statusText,
|
||||
timeElapsedMs: lastDatum.response.timeMs,
|
||||
requestHeader: lastDatum.request.header,
|
||||
responseHeader: lastDatum.response.header,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Console = (params:props) => {
|
||||
|
||||
const registryRef = useRef(new PanelRegistry());
|
||||
// const [consoleInputKey] = useMemo(()=>{
|
||||
// return [selectedCluster.id + '-console-input'];
|
||||
|
|
|
@ -11,7 +11,7 @@ import './ConsoleInput.scss';
|
|||
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es';
|
||||
import { useSetInputEditor } from '../hooks/use_set_input_editor';
|
||||
import '@elastic/eui/dist/eui_theme_light.css';
|
||||
import { instance as registry, editorList } from '../contexts/editor_context/editor_registry';
|
||||
import { instance as registry } from '../contexts/editor_context/editor_registry';
|
||||
import 'antd/dist/antd.css';
|
||||
import {retrieveAutoCompleteInfo} from '../modules/mappings/mappings';
|
||||
import {useSaveCurrentTextObject} from '../hooks/use_save_current_text_object';
|
||||
|
@ -109,7 +109,6 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
|
|||
editorInstanceRef.current = senseEditor;
|
||||
setInputEditor(senseEditor);
|
||||
senseEditor.paneKey = paneKey;
|
||||
editorList.addInputEditor(senseEditor);
|
||||
senseEditor.update(initialText || DEFAULT_INPUT_VALUE);
|
||||
applyCurrentSettings(senseEditor!.getCoreEditor(), {fontSize:12, wrapMode: true,});
|
||||
|
||||
|
@ -151,8 +150,12 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
|
|||
aceEditorRef.current && (aceEditorRef.current['clusterID'] = clusterID);
|
||||
},[clusterID])
|
||||
|
||||
const handleSaveAsCommonCommand = async () => {
|
||||
const editor = registry.getInputEditor();
|
||||
const handleSaveAsCommonCommand = async () => {
|
||||
const editor = editorInstanceRef.current;
|
||||
if(editor == null){
|
||||
console.warn('editor is null')
|
||||
return
|
||||
}
|
||||
const requests = await editor.getRequestsInRange();
|
||||
const formattedRequest = requests.map(request => ({
|
||||
method: request.method,
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
.request-status-bar{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
.bar-item{
|
||||
flex: 0 0 50%;
|
||||
.base-info{
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
.info-item{
|
||||
margin: 12px;
|
||||
position: relative;
|
||||
&.health{
|
||||
padding-right: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.status_info{
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
.info-item{
|
||||
margin: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,10 @@
|
|||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip,EuiCodeBlock } from '@elastic/eui';
|
||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||
import './request_status_bar.scss';
|
||||
import {Drawer, Tabs, Button} from 'antd';
|
||||
|
||||
export interface Props {
|
||||
requestInProgress: boolean;
|
||||
|
@ -37,6 +40,8 @@ export interface Props {
|
|||
|
||||
// The time, in milliseconds, that the last request took
|
||||
timeElapsedMs: number;
|
||||
responseHeader: string;
|
||||
requestHeader: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -60,17 +65,103 @@ const mapStatusCodeToBadgeColor = (statusCode: number) => {
|
|||
return 'danger';
|
||||
};
|
||||
|
||||
export const RequestStatusBar: FunctionComponent<Props> = ({
|
||||
// export const RequestStatusBar: FunctionComponent<Props> = ({
|
||||
// requestInProgress,
|
||||
// requestResult,
|
||||
// selectedCluster,
|
||||
// }) => {
|
||||
// let content: React.ReactNode = null;
|
||||
// const clusterContent = (<EuiFlexItem grow={false} style={{marginRight:'auto'}}>
|
||||
// <EuiBadge style={{position:'relative', paddingLeft: 20}}>
|
||||
// <i style={{marginRight:3, position:'absolute', top: 1, left:3}}><HealthStatusCircle status={selectedCluster.status}/></i>{selectedCluster.host} - {selectedCluster.version}
|
||||
// </EuiBadge>
|
||||
// </EuiFlexItem>);
|
||||
|
||||
// if (requestInProgress) {
|
||||
// content = (
|
||||
// <EuiFlexItem grow={false}>
|
||||
// <EuiBadge color="hollow">
|
||||
// Request in progress
|
||||
// </EuiBadge>
|
||||
// </EuiFlexItem>
|
||||
// );
|
||||
// } else if (requestResult) {
|
||||
// const { endpoint, method, statusCode, statusText, timeElapsedMs } = requestResult;
|
||||
|
||||
// content = (
|
||||
// <>
|
||||
// <EuiFlexItem grow={false}>
|
||||
// <EuiToolTip
|
||||
// position="top"
|
||||
// content={
|
||||
// <EuiText size="s">{`${method} ${
|
||||
// endpoint.startsWith('/') ? endpoint : '/' + endpoint
|
||||
// }`}</EuiText>
|
||||
// }
|
||||
// >
|
||||
// <EuiBadge color={mapStatusCodeToBadgeColor(statusCode)}>
|
||||
// {/* Use to ensure that no matter the width we don't allow line breaks */}
|
||||
// {statusCode} - {statusText}
|
||||
// </EuiBadge>
|
||||
// </EuiToolTip>
|
||||
// </EuiFlexItem>
|
||||
// <EuiFlexItem grow={false}>
|
||||
// <EuiToolTip
|
||||
// position="top"
|
||||
// content={
|
||||
// <EuiText size="s">
|
||||
// Time Elapsed
|
||||
// </EuiText>
|
||||
// }
|
||||
// >
|
||||
// <EuiText size="s">
|
||||
// <EuiBadge color="default">
|
||||
// {timeElapsedMs} {'ms'}
|
||||
// </EuiBadge>
|
||||
// </EuiText>
|
||||
// </EuiToolTip>
|
||||
// </EuiFlexItem>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <EuiFlexGroup
|
||||
// justifyContent="flexEnd"
|
||||
// alignItems="center"
|
||||
// direction="row"
|
||||
// gutterSize="s"
|
||||
// responsive={false}
|
||||
// >
|
||||
// {clusterContent}
|
||||
// {content}
|
||||
// </EuiFlexGroup>
|
||||
// );
|
||||
// };
|
||||
|
||||
export const RequestStatusBar = ({
|
||||
requestInProgress,
|
||||
requestResult,
|
||||
selectedCluster,
|
||||
}) => {
|
||||
}:Props) => {
|
||||
let content: React.ReactNode = null;
|
||||
const clusterContent = (<EuiFlexItem grow={false} style={{marginRight:'auto'}}>
|
||||
<EuiBadge>
|
||||
{selectedCluster.host} - {selectedCluster.version}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>);
|
||||
const clusterContent = (<div className="base-info">
|
||||
<div className="info-item health">
|
||||
<span>健康状态:</span>
|
||||
<i style={{position:'absolute', top: 1, right:0}}>
|
||||
<HealthStatusCircle status={selectedCluster.status}/>
|
||||
</i>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>集群地址:</span>
|
||||
{selectedCluster.host}
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>版本:</span>
|
||||
{selectedCluster.version}
|
||||
</div>
|
||||
</div>);
|
||||
const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
|
||||
|
||||
if (requestInProgress) {
|
||||
content = (
|
||||
|
@ -85,7 +176,8 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
|||
|
||||
content = (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div className="status_info">
|
||||
<div className="info-item">
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
|
@ -99,8 +191,8 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
|||
{statusCode} - {statusText}
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
|
@ -115,21 +207,48 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
|||
</EuiBadge>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<EuiText size="s">
|
||||
<Button type="link" onClick={()=>{setHeaderInfoVisible(true)}}>
|
||||
Headers
|
||||
</Button>
|
||||
</EuiText>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="flexEnd"
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
{clusterContent}
|
||||
{content}
|
||||
</EuiFlexGroup>
|
||||
<div className="request-status-bar">
|
||||
<div className="bar-item">{clusterContent}</div>
|
||||
<div className="bar-item">{content}</div>
|
||||
<Drawer title="Request header info"
|
||||
style={{zIndex:1004}}
|
||||
width={520}
|
||||
|
||||
destroyOnClose={true}
|
||||
visible={headerInfoVisible}
|
||||
onClose={()=>{setHeaderInfoVisible(false)}}
|
||||
>
|
||||
<Tabs>
|
||||
<Tabs.TabPane tab="Request" key="1">
|
||||
<div>
|
||||
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
||||
{requestResult?.requestHeader}
|
||||
</EuiCodeBlock>
|
||||
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Response" key="2">
|
||||
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
||||
{requestResult?.responseHeader}
|
||||
</EuiCodeBlock>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Drawer>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
|
|
@ -37,7 +37,6 @@ export class EditorRegistry {
|
|||
|
||||
setInputEditor(inputEditor: SenseEditor) {
|
||||
this.inputEditor = inputEditor;
|
||||
inputEditor.setAutocompleter();
|
||||
}
|
||||
|
||||
getInputEditor() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TokensProvider } from './tokens_provider';
|
||||
import { Token } from './token';
|
||||
import RowParser from './row_parser';
|
||||
|
||||
type MarkerRef = any;
|
||||
|
||||
|
@ -81,6 +82,9 @@ export enum LINE_MODE {
|
|||
* being used which is usually vendor code such as Ace or Monaco.
|
||||
*/
|
||||
export interface CoreEditor {
|
||||
getParser(): RowParser;
|
||||
getAutocompleter(): AutoCompleterFunction;
|
||||
|
||||
/**
|
||||
* Get the current position of the cursor.
|
||||
*/
|
||||
|
|
|
@ -12,16 +12,16 @@ export class SenseEditor {
|
|||
currentReqRange: (Range & { markerRef: unknown }) | null;
|
||||
parser: RowParser;
|
||||
|
||||
private readonly autocomplete: ReturnType<typeof createAutocompleter>;
|
||||
// private readonly autocomplete: ReturnType<typeof createAutocompleter>;
|
||||
|
||||
constructor(private readonly coreEditor: CoreEditor) {
|
||||
this.currentReqRange = null;
|
||||
this.parser = new RowParser(this.coreEditor);
|
||||
this.autocomplete = createAutocompleter({
|
||||
coreEditor,
|
||||
parser: this.parser,
|
||||
});
|
||||
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
||||
// this.parser = new RowParser(this.coreEditor);
|
||||
// this.autocomplete = createAutocompleter({
|
||||
// coreEditor,
|
||||
// });
|
||||
// this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
||||
this.parser = coreEditor.getParser();
|
||||
this.coreEditor.on(
|
||||
'tokenizerUpdate',
|
||||
this.highlightCurrentRequestsAndUpdateActionBar.bind(this)
|
||||
|
@ -30,10 +30,6 @@ export class SenseEditor {
|
|||
this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this));
|
||||
}
|
||||
|
||||
setAutocompleter = ()=>{
|
||||
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
||||
}
|
||||
|
||||
prevRequestStart = (rowOrPos?: number | Position): Position => {
|
||||
let curRow: number;
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import { instance as registry } from '../../contexts/editor_context/editor_regis
|
|||
import { useRequestActionContext } from '../../contexts/request_context';
|
||||
import { useServicesContext } from '../../contexts/services_context';
|
||||
import {getCommand} from '../../modules/mappings/mappings';
|
||||
import {useEditorReadContext} from '../../contexts/editor_context';
|
||||
|
||||
function buildRawCommonCommandRequest(cmd:any){
|
||||
const {requests} = cmd._source;
|
||||
|
@ -48,10 +49,12 @@ function buildRawCommonCommandRequest(cmd:any){
|
|||
export const useSendCurrentRequestToES = () => {
|
||||
const dispatch = useRequestActionContext();
|
||||
const { services: { history }, clusterID } = useServicesContext();
|
||||
const {sensorEditor:editor} = useEditorReadContext();
|
||||
|
||||
return useCallback(async () => {
|
||||
try {
|
||||
const editor = registry.getInputEditor();
|
||||
// const editor = registry.getInputEditor();
|
||||
if(!editor) return
|
||||
const requests = await editor.getRequestsInRange();
|
||||
if (!requests.length) {
|
||||
console.log('No request selected. Select a request by placing the cursor inside it.');
|
||||
|
@ -118,5 +121,5 @@ export const useSendCurrentRequestToES = () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [dispatch, history, clusterID]);
|
||||
}, [dispatch, history, clusterID, editor]);
|
||||
};
|
||||
|
|
|
@ -74,7 +74,6 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
|
||||
|
||||
const isSuccess =
|
||||
|
@ -83,7 +82,10 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
||||
|
||||
if (isSuccess) {
|
||||
let value = xhr.responseText;
|
||||
// let value = xhr.responseText;
|
||||
let resObj = JSON.parse(xhr.responseText)
|
||||
|
||||
let value = resObj.response_body;
|
||||
|
||||
const warnings = xhr.getResponseHeader('warning');
|
||||
if (warnings) {
|
||||
|
@ -102,11 +104,13 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
statusText: xhr.statusText,
|
||||
contentType: xhr.getResponseHeader('Content-Type'),
|
||||
value,
|
||||
header: resObj.response_header,
|
||||
},
|
||||
request: {
|
||||
data: esData,
|
||||
method: esMethod,
|
||||
path: esPath,
|
||||
header: resObj.request_header,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -116,8 +120,15 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
let value;
|
||||
let contentType: string;
|
||||
if (xhr.responseText) {
|
||||
value = xhr.responseText; // ES error should be shown
|
||||
contentType = xhr.getResponseHeader('Content-Type');
|
||||
const resObj = JSON.parse(xhr.responseText)
|
||||
if(resObj.error){
|
||||
value = resObj.error;
|
||||
contentType = 'text/plain';
|
||||
}else{
|
||||
value = resObj.response_body; // ES error should be shown
|
||||
contentType = xhr.getResponseHeader('Content-Type');
|
||||
}
|
||||
|
||||
} else {
|
||||
value = 'Request failed to get to the server (status code: ' + xhr.status + ')';
|
||||
contentType = 'text/plain';
|
||||
|
|
|
@ -41,7 +41,7 @@ export const useSetInputEditor = () => {
|
|||
return useCallback(
|
||||
(editor: SenseEditor) => {
|
||||
dispatch({ type: 'setInputEditor', payload: editor });
|
||||
registry.setInputEditor(editor);
|
||||
// registry.setInputEditor(editor);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
|
|
@ -360,13 +360,10 @@ function addMetaToTermsList(
|
|||
// eslint-disable-next-line
|
||||
export default function ({
|
||||
coreEditor: editor,
|
||||
parser,
|
||||
}: {
|
||||
coreEditor: CoreEditor;
|
||||
parser: RowParser;
|
||||
}) {
|
||||
|
||||
|
||||
const parser = new RowParser(editor)
|
||||
function applyTerm(term: {
|
||||
value?: string;
|
||||
context?: AutoCompleteContext;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
import $ from 'jquery';
|
||||
// @ts-ignore
|
||||
import { stringify } from 'query-string';
|
||||
import {pathPrefix} from '@/services/common';
|
||||
|
||||
interface SendOptions {
|
||||
asSystemRequest?: boolean;
|
||||
|
@ -103,12 +104,7 @@ export function send(
|
|||
}
|
||||
|
||||
export function queryCommonCommands(title?: string) {
|
||||
const clusterID = extractClusterIDFromURL();
|
||||
if(!clusterID){
|
||||
console.log('can not get clusterid from url');
|
||||
return;
|
||||
}
|
||||
let url = `/elasticsearch/${clusterID}/command/_search`;
|
||||
let url = `${pathPrefix}/elasticsearch/command`;
|
||||
if(title){
|
||||
url +=`?title=${title}`
|
||||
}
|
||||
|
@ -124,12 +120,7 @@ export function constructESUrl(baseUri: string, path: string) {
|
|||
}
|
||||
|
||||
export function saveCommonCommand(params: any) {
|
||||
const clusterID = extractClusterIDFromURL();
|
||||
if(!clusterID){
|
||||
console.log('can not get clusterid from url');
|
||||
return;
|
||||
}
|
||||
return fetch(`/elasticsearch/${clusterID}/command`, {
|
||||
return fetch(`${pathPrefix}/elasticsearch/command`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params),
|
||||
headers:{
|
||||
|
|
|
@ -10,28 +10,58 @@ import { EditorEvent, AutoCompleterFunction } from '../../entities/core_editor';
|
|||
import { AceTokensProvider } from '../../entities/ace_tokens_providers';
|
||||
import * as curl from './curl';
|
||||
import smartResize from './smart_resize';
|
||||
ace.define(
|
||||
'ace/autocomplete/text_completer',
|
||||
['require', 'exports', 'module'],
|
||||
function (
|
||||
require: unknown,
|
||||
exports: {
|
||||
getCompletions: (
|
||||
innerEditor: unknown,
|
||||
session: unknown,
|
||||
pos: unknown,
|
||||
prefix: unknown,
|
||||
callback: (e: null | Error, values: string[]) => void
|
||||
) => void;
|
||||
}
|
||||
) {
|
||||
exports.getCompletions = function (innerEditor, session, pos, prefix, callback) {
|
||||
callback(null, []);
|
||||
};
|
||||
}
|
||||
);
|
||||
import createAutocompleter from '../../modules/autocomplete/autocomplete';
|
||||
import RowParser from '../../entities/row_parser';
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
(function initAceEditor() {
|
||||
ace.define(
|
||||
'ace/autocomplete/text_completer',
|
||||
['require', 'exports', 'module'],
|
||||
function (
|
||||
require: unknown,
|
||||
exports: {
|
||||
getCompletions: (
|
||||
innerEditor: unknown,
|
||||
session: unknown,
|
||||
pos: unknown,
|
||||
prefix: unknown,
|
||||
callback: (e: null | Error, values: string[]) => void
|
||||
) => void;
|
||||
}
|
||||
) {
|
||||
exports.getCompletions = function (innerEditor, session, pos, prefix, callback) {
|
||||
callback(null, []);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
||||
langTools.setCompleters( //addCompleters
|
||||
[{
|
||||
identifierRegexps: [
|
||||
/[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/, // adds support for dot character
|
||||
],
|
||||
getCompletions: (
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
DO_NOT_USE_1: IAceEditor,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
DO_NOT_USE_2: IAceEditSession,
|
||||
pos: { row: number; column: number },
|
||||
prefix: string,
|
||||
callback: (...args: unknown[]) => void
|
||||
) => {
|
||||
const {coreEditor} = DO_NOT_USE_1;
|
||||
const position: Position = {
|
||||
lineNumber: pos.row + 1,
|
||||
column: pos.column + 1,
|
||||
};
|
||||
coreEditor.autocompleter(position, prefix, callback);
|
||||
// autocompleter(position, prefix, callback);
|
||||
},
|
||||
}],
|
||||
);
|
||||
})();
|
||||
|
||||
// @ts-ignore
|
||||
import * as InputMode from './mode/input';
|
||||
|
@ -45,10 +75,16 @@ export class LegacyCoreEditor implements CoreEditor {
|
|||
// @ts-ignore
|
||||
$actions: JQuery<HTMLElement>;
|
||||
resize: () => void;
|
||||
private autocompleter: AutoCompleterFunction;
|
||||
private parser: RowParser;
|
||||
|
||||
constructor(private readonly editor: IAceEditor, actions: HTMLElement) {
|
||||
constructor(private editor: IAceEditor, actions: HTMLElement) {
|
||||
this.$actions = $(actions);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
this.parser = new RowParser(this);
|
||||
this.autocompleter = createAutocompleter({
|
||||
coreEditor: this,
|
||||
}).getCompletions
|
||||
|
||||
const session = this.editor.getSession();
|
||||
// @ts-ignore
|
||||
|
@ -72,6 +108,14 @@ export class LegacyCoreEditor implements CoreEditor {
|
|||
this.editor.$blockScrolling = Infinity;
|
||||
this.hideActionsBar();
|
||||
this.editor.focus();
|
||||
editor.coreEditor = this;
|
||||
}
|
||||
|
||||
getParser(): RowParser {
|
||||
return this.parser;
|
||||
}
|
||||
getAutocompleter(): AutoCompleterFunction {
|
||||
return this.autocompleter;
|
||||
}
|
||||
|
||||
// dirty check for tokenizer state, uses a lot less cycles
|
||||
|
@ -388,6 +432,7 @@ export class LegacyCoreEditor implements CoreEditor {
|
|||
prefix: string,
|
||||
callback: (...args: unknown[]) => void
|
||||
) => {
|
||||
debugger
|
||||
const position: Position = {
|
||||
lineNumber: pos.row + 1,
|
||||
column: pos.column + 1,
|
||||
|
|
|
@ -223,15 +223,23 @@ function getFieldNamesFromProperties(properties = {}) {
|
|||
}
|
||||
|
||||
function loadTemplates(templatesObject = {}, clusterID) {
|
||||
templatesObject = getRawBody(templatesObject);
|
||||
templates[clusterID] = Object.keys(templatesObject);
|
||||
}
|
||||
|
||||
function getRawBody(body) {
|
||||
if(body.response_body){
|
||||
return JSON.parse(body.response_body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
export function loadMappings(mappings, clusterID) {
|
||||
mappings = getRawBody(mappings)
|
||||
let clusterPerIndexTypes = {};
|
||||
|
||||
$.each(mappings, function (index, indexMapping) {
|
||||
const normalizedIndexMappings = {};
|
||||
|
||||
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
||||
if (indexMapping.mappings && _.keys(indexMapping).length === 1) {
|
||||
indexMapping = indexMapping.mappings;
|
||||
|
@ -242,7 +250,7 @@ export function loadMappings(mappings, clusterID) {
|
|||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
} else {
|
||||
normalizedIndexMappings[typeName] = [];
|
||||
normalizedIndexMappings[typeName] = getFieldNamesFromProperties(typeMapping.properties); // for es 2.x, 5.x, 6.x
|
||||
}
|
||||
});
|
||||
clusterPerIndexTypes[index] = normalizedIndexMappings;
|
||||
|
@ -251,6 +259,7 @@ export function loadMappings(mappings, clusterID) {
|
|||
}
|
||||
|
||||
export function loadAliases(aliases, clusterID) {
|
||||
aliases = getRawBody(aliases)
|
||||
let clusterPerAliasIndexes = {};
|
||||
$.each(aliases || {}, function (index, omdexAliases) {
|
||||
// verify we have an index defined. useful when mapping loading is disabled
|
||||
|
|
|
@ -39,12 +39,14 @@ import { SenseEditor } from '../entities/sense_editor';
|
|||
export interface Store {
|
||||
ready: boolean;
|
||||
currentTextObject: TextObject | null;
|
||||
sensorEditor: SenseEditor | null;
|
||||
}
|
||||
|
||||
export const initialValue: Store = produce<Store>(
|
||||
{
|
||||
ready: false,
|
||||
currentTextObject: null,
|
||||
sensorEditor: null,
|
||||
},
|
||||
identity
|
||||
);
|
||||
|
@ -58,7 +60,8 @@ export const reducer: Reducer<Store, Action> = (state, action) =>
|
|||
if (action.type === 'setInputEditor') {
|
||||
if (action.payload) {
|
||||
draft.ready = true;
|
||||
}
|
||||
draft.sensorEditor = action.payload;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ function useStorage(key, defaultValue, storageObject, encryptor) {
|
|||
return storeValue
|
||||
}
|
||||
|
||||
if (typeof initialValue === "function") {
|
||||
if (typeof defaultValue === "function") {
|
||||
return defaultValue()
|
||||
} else {
|
||||
return defaultValue
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect, useRef } from "react"
|
||||
|
||||
export default function useEventListener(
|
||||
eventType,
|
||||
callback,
|
||||
element = window
|
||||
) {
|
||||
const callbackRef = useRef(callback)
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback
|
||||
}, [callback])
|
||||
|
||||
useEffect(() => {
|
||||
if (element == null) return
|
||||
const handler = e => callbackRef.current(e)
|
||||
element.addEventListener(eventType, handler)
|
||||
|
||||
return () => element.removeEventListener(eventType, handler)
|
||||
}, [eventType, element])
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { useCallback, useEffect, useRef } from "react"
|
||||
|
||||
export default function useTimeout(callback, delay) {
|
||||
const callbackRef = useRef(callback)
|
||||
const timeoutRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback
|
||||
}, [callback])
|
||||
|
||||
const set = useCallback(() => {
|
||||
timeoutRef.current = setTimeout(() => callbackRef.current(), delay)
|
||||
}, [delay])
|
||||
|
||||
const clear = useCallback(() => {
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
set()
|
||||
return clear
|
||||
}, [delay, set, clear])
|
||||
|
||||
const reset = useCallback(() => {
|
||||
clear()
|
||||
set()
|
||||
}, [clear, set])
|
||||
|
||||
return { reset, clear }
|
||||
}
|
|
@ -4,6 +4,7 @@ import {searchClusterConfig, getClusterStatus} from "@/services/cluster";
|
|||
import {formatESSearchResult, extractClusterIDFromURL} from '@/lib/elasticsearch/util';
|
||||
import {Modal} from 'antd';
|
||||
import router from "umi/router";
|
||||
import _ from 'lodash';
|
||||
|
||||
const MENU_COLLAPSED_KEY = "search-center:menu:collapsed";
|
||||
|
||||
|
@ -153,21 +154,24 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
*fetchClusterStatus({payload}, {call, put}){
|
||||
*fetchClusterStatus({payload}, {call, put, select}){
|
||||
let res = yield call(getClusterStatus, payload);
|
||||
if(!res){
|
||||
return false
|
||||
}
|
||||
const {clusterStatus} = yield select(state=>state.global);
|
||||
if(res.error){
|
||||
console.log(res.error)
|
||||
return false;
|
||||
}
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
clusterStatus: res
|
||||
}
|
||||
});
|
||||
if(!_.isEqual(res, clusterStatus)){
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
clusterStatus: res
|
||||
}
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
|
@ -251,7 +255,7 @@ export default {
|
|||
// Subscribe history(url) change, trigger `load` action if pathname is `/`
|
||||
return history.listen(({ pathname, search }) => {
|
||||
let clusterVisible = true;
|
||||
const clusterHiddenPath = ["/system", "/cluster/overview", "/alerting/overview", "/alerting/monitor/monitors/", "/alerting/destination"];
|
||||
const clusterHiddenPath = ["/system", "/cluster/overview", "/alerting/overview", "/alerting/monitor/monitors/", "/alerting/destination", '/dev_tool'];
|
||||
if(clusterHiddenPath.some(p=>pathname.startsWith(p))){
|
||||
clusterVisible = false;
|
||||
if(pathname.includes("elasticsearch")){
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import Console from '../../components/kibana/console/components/Console';
|
||||
import {connect} from 'dva';
|
||||
import {Tabs, Button, Icon, Menu, Dropdown} from 'antd';
|
||||
import {useState, useReducer, useCallback, useEffect, useMemo} from 'react';
|
||||
import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react';
|
||||
import {useLocalStorage} from '@/lib/hooks/storage';
|
||||
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings';
|
||||
import {editorList} from '@/components/kibana/console/contexts/editor_context/editor_registry';
|
||||
import {TabTitle} from './console_tab_title';
|
||||
import '@/assets/utility.scss';
|
||||
import { Resizable } from "re-resizable";
|
||||
import {ResizeBar} from '@/components/infini/resize_bar';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
|
@ -25,7 +28,7 @@ const addTab = (state: any, action: any) => {
|
|||
const { panes } = state;
|
||||
const {cluster} = action.payload;
|
||||
const activeKey = `${cluster.id}:${new Date().valueOf()}`;
|
||||
panes.push({ key: activeKey, cluster_id: cluster.id});
|
||||
panes.push({ key: activeKey, cluster_id: cluster.id, title: cluster.name});
|
||||
return {
|
||||
...state,
|
||||
panes,
|
||||
|
@ -58,7 +61,22 @@ const consoleTabReducer = (state: any, action: any) => {
|
|||
...state,
|
||||
activeKey: payload.activeKey,
|
||||
}
|
||||
editorList.setActiveEditor(payload.activeKey);
|
||||
break;
|
||||
case 'saveTitle':
|
||||
const {key, title} = action.payload;
|
||||
const newPanes = state.panes.map((pane: any)=>{
|
||||
if(pane.key == key){
|
||||
return {
|
||||
...pane,
|
||||
title,
|
||||
}
|
||||
}
|
||||
return pane;
|
||||
});
|
||||
newState = {
|
||||
...state,
|
||||
panes: newPanes,
|
||||
}
|
||||
break;
|
||||
case 'saveContent':
|
||||
const panes = state.panes.map((pane)=>{
|
||||
|
@ -81,24 +99,59 @@ const consoleTabReducer = (state: any, action: any) => {
|
|||
return newState;
|
||||
}
|
||||
|
||||
const ConsoleUI = ({clusterList}: any)=>{
|
||||
function calcHeightToPX(height: string){
|
||||
const intHeight = parseInt(height)
|
||||
if(height.endsWith('vh')){
|
||||
return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) * intHeight / 100;
|
||||
}else{
|
||||
return intHeight;
|
||||
}
|
||||
}
|
||||
|
||||
export const ConsoleUI = ({selectedCluster,
|
||||
clusterList,
|
||||
clusterStatus,
|
||||
minimize=false,
|
||||
onMinimizeClick,
|
||||
resizeable=false,
|
||||
height='50vh'
|
||||
}: any)=>{
|
||||
const clusterMap = useMemo(()=>{
|
||||
let cm = {};
|
||||
if(!clusterStatus){
|
||||
return cm;
|
||||
}
|
||||
(clusterList || []).map((cluster: any)=>{
|
||||
cm[cluster.id] = cluster;
|
||||
cluster.status = clusterStatus[cluster.id].health?.status;
|
||||
if(!clusterStatus[cluster.id].available){
|
||||
cluster.status = 'unavailable';
|
||||
}
|
||||
cm[cluster.id] = cluster;
|
||||
});
|
||||
return cm;
|
||||
}, [clusterList])
|
||||
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", {
|
||||
panes: [],
|
||||
activeKey: '',
|
||||
},{
|
||||
}, [clusterList, clusterStatus])
|
||||
const initialDefaultState = ()=>{
|
||||
const defaultActiveKey = `${selectedCluster.id}:${new Date().valueOf()}`;
|
||||
const defaultState = selectedCluster? {
|
||||
panes:[{
|
||||
key: defaultActiveKey, cluster_id: selectedCluster.id, title: selectedCluster.name
|
||||
}],
|
||||
activeKey: defaultActiveKey,
|
||||
}: {panes:[],activeKey:''};
|
||||
return defaultState
|
||||
}
|
||||
|
||||
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", initialDefaultState, {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
})
|
||||
const [tabState, dispatch] = useReducer(consoleTabReducer, localState)
|
||||
|
||||
useEffect(()=>{
|
||||
if(tabState.panes.length == 0){
|
||||
removeLocalState()
|
||||
return
|
||||
}
|
||||
setLocalState(tabState)
|
||||
}, [tabState])
|
||||
|
||||
|
@ -150,20 +203,96 @@ const ConsoleUI = ({clusterList}: any)=>{
|
|||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const rootRef = useRef(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const fullscreenClick = ()=>{
|
||||
if(rootRef.current != null){
|
||||
if(!isFullscreen){
|
||||
rootRef.current.className = rootRef.current.className + " fullscreen";
|
||||
// rootRef.current.style.overflow = 'scroll';
|
||||
}else{
|
||||
rootRef.current.className = rootRef.current.className.replace(' fullscreen', '');
|
||||
}
|
||||
}
|
||||
setEditorHeight(rootRef.current.clientHeight)
|
||||
setIsFullscreen(!isFullscreen)
|
||||
}
|
||||
|
||||
const tabBarExtra =(<Dropdown overlay={menu}>
|
||||
<Button size="small">
|
||||
<Icon type="plus"/>
|
||||
const tabBarExtra =(
|
||||
<div>
|
||||
<Dropdown overlay={menu}>
|
||||
<Button size="small" style={{marginRight:5}}>
|
||||
<Icon type="plus"/>
|
||||
</Button>
|
||||
</Dropdown>);
|
||||
</Dropdown>
|
||||
{isFullscreen?
|
||||
<Button size="small" onClick={fullscreenClick}>
|
||||
<Icon type="fullscreen-exit"/>
|
||||
</Button>:
|
||||
<Button size="small" onClick={fullscreenClick}>
|
||||
<Icon type="fullscreen"/>
|
||||
</Button>
|
||||
}
|
||||
{minimize? <Button size="small" onClick={onMinimizeClick} style={{marginLeft:5}}>
|
||||
<Icon type="minus"/>
|
||||
</Button>:null}
|
||||
</div>
|
||||
);
|
||||
|
||||
setClusterID(tabState.activeKey?.split(':')[0]);
|
||||
const panes = tabState.panes.filter((pane: any)=>{
|
||||
return typeof clusterMap[pane.cluster_id] != 'undefined';
|
||||
})
|
||||
|
||||
const saveTitle = (key: string, title: string)=>{
|
||||
dispatch({
|
||||
type:'saveTitle',
|
||||
payload: {
|
||||
key,
|
||||
title,
|
||||
}
|
||||
})
|
||||
}
|
||||
const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height))
|
||||
const onResize = (_env, _dir, refToElement, delta)=>{
|
||||
// console.log(refToElement.offsetHeight, delta)
|
||||
setEditorHeight(refToElement.clientHeight)
|
||||
}
|
||||
|
||||
const disableWindowScroll = ()=>{
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
const enableWindowScroll = ()=>{
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div style={{background:'#fff'}}>
|
||||
<Resizable
|
||||
defaultSize={{
|
||||
height: editorHeight||'50vh'
|
||||
}}
|
||||
minHeight={200}
|
||||
maxHeight="100vh"
|
||||
handleComponent={{ top: <ResizeBar/> }}
|
||||
onResize={onResize}
|
||||
enable={{
|
||||
top: resizeable,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}>
|
||||
<div style={{background:'#fff', height:'100%'}}
|
||||
onMouseOver={disableWindowScroll}
|
||||
onMouseOut={enableWindowScroll}
|
||||
id="console"
|
||||
ref={rootRef} >
|
||||
<Tabs
|
||||
onChange={onChange}
|
||||
activeKey={tabState.activeKey}
|
||||
|
@ -173,13 +302,14 @@ const ConsoleUI = ({clusterList}: any)=>{
|
|||
tabBarExtraContent={tabBarExtra}
|
||||
>
|
||||
{panes.map(pane => (
|
||||
<TabPane tab={clusterMap[pane.cluster_id].name} key={pane.key} closable={pane.closable}>
|
||||
<TabConsole selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
||||
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
||||
<TabConsole height={editorHeight - 40} selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
||||
{/* {pane.content} */}
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</Resizable>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -188,4 +318,6 @@ export default connect(({
|
|||
})=>({
|
||||
selectedCluster: global.selectedCluster,
|
||||
clusterList: global.clusterList,
|
||||
clusterStatus: global.clusterStatus,
|
||||
height: window.innerHeight - 75 + 'px',
|
||||
}))(ConsoleUI);
|
|
@ -0,0 +1,19 @@
|
|||
.tab-title{
|
||||
display: inline-block;
|
||||
.input-eidtor{
|
||||
border-radius: 0;
|
||||
border:none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-right: none;
|
||||
padding-left: 1em;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#console{
|
||||
.ant-tabs-bar{
|
||||
margin: 0px 0 5px 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import {useState, useRef, useEffect} from 'react';
|
||||
import './console_tab_title.scss';
|
||||
|
||||
interface TabTitleProps {
|
||||
title: string,
|
||||
onTitleChange?: (title: string)=>void;
|
||||
}
|
||||
|
||||
export const TabTitle = ({title, onTitleChange}: TabTitleProps)=>{
|
||||
const [editable, setEditable] = useState(false);
|
||||
const [value, setValue] = useState(title);
|
||||
const onValueChange = (e: any)=>{
|
||||
const newVal = e.target.value;
|
||||
setValue(newVal);
|
||||
if(typeof onTitleChange == 'function') onTitleChange(newVal);
|
||||
}
|
||||
useEffect(()=>{
|
||||
if(editable){
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
},[editable])
|
||||
const inputRef = useRef(null);
|
||||
return (<div title="double click to change title" className="tab-title" onDoubleClick={()=>{
|
||||
setEditable(true)
|
||||
}}>
|
||||
{editable ? <input ref={inputRef} className="input-eidtor"
|
||||
type="text" value={value}
|
||||
onBlur={()=>{
|
||||
setEditable(false)
|
||||
}}
|
||||
onChange={onValueChange}/>:value}
|
||||
</div>)
|
||||
}
|
||||
|
Loading…
Reference in New Issue