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.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(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
|
//new api
|
||||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)
|
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)
|
||||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview/alerts"), alerting.GetAlerts)
|
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.GET, "/elasticsearch/:id/alerting/alerts", alerting.GetAlerts)
|
||||||
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_monitors/:monitorID/_acknowledge/alerts", alerting.AcknowledgeAlerts)
|
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_monitors/:monitorID/_acknowledge/alerts", alerting.AcknowledgeAlerts)
|
||||||
|
|
||||||
|
|
||||||
task.RegisterScheduleTask(task.ScheduleTask{
|
task.RegisterScheduleTask(task.ScheduleTask{
|
||||||
Description: "sync reindex task result",
|
Description: "sync reindex task result",
|
||||||
Task: func() {
|
Task: func() {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
const LastCommitLog = "N/A"
|
const LastCommitLog = "b8fb6a3, Fri Oct 15 11:41:38 2021 +0800, liugq, console tab v0.1 "
|
||||||
const BuildDate = "N/A"
|
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 HeaderSearch from '../HeaderSearch';
|
||||||
import SelectLang from '../SelectLang';
|
import SelectLang from '../SelectLang';
|
||||||
import styles from './index.less';
|
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 {
|
export default class GlobalHeaderRight extends PureComponent {
|
||||||
|
state={consoleVisible: false}
|
||||||
getNoticeData() {
|
getNoticeData() {
|
||||||
const { notices = [] } = this.props;
|
const { notices = [] } = this.props;
|
||||||
if (notices.length === 0) {
|
if (notices.length === 0) {
|
||||||
|
@ -95,7 +99,10 @@ export default class GlobalHeaderRight extends PureComponent {
|
||||||
|
|
||||||
<a className={styles.action} onClick={()=>{
|
<a className={styles.action} onClick={()=>{
|
||||||
const {history, selectedCluster} = this.props;
|
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>
|
}}> <Icon type="code" /></a>
|
||||||
|
|
||||||
{/* <NoticeIcon
|
{/* <NoticeIcon
|
||||||
|
@ -151,7 +158,52 @@ export default class GlobalHeaderRight extends PureComponent {
|
||||||
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
|
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
|
||||||
)} */}
|
)} */}
|
||||||
<SelectLang className={styles.action} />
|
<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>
|
</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> = {
|
const statusColorMap: Record<string, string> = {
|
||||||
'green': '#39b362',
|
'green': '#39b362',
|
||||||
|
@ -15,6 +16,9 @@ interface props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HealthStatusCircle = ({status}: 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);
|
const color = convertStatusToColor(status);
|
||||||
return <div style={{background: color, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #999', display: 'inline-block'}}></div>
|
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 (
|
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 layout="vertical">
|
||||||
<Form.Item label="标题">
|
<Form.Item label="标题">
|
||||||
{form.getFieldDecorator('title', {
|
{form.getFieldDecorator('title', {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import React, { useRef, useMemo,useEffect } from 'react';
|
import React, { useRef, useMemo,useEffect, useLayoutEffect } from 'react';
|
||||||
import ConsoleInput from './ConsoleInput';
|
import ConsoleInput from './ConsoleInput';
|
||||||
import ConsoleOutput from './ConsoleOutput';
|
import ConsoleOutput from './ConsoleOutput';
|
||||||
import { Panel } from './Panel';
|
import { Panel } from './Panel';
|
||||||
|
@ -14,12 +14,14 @@ import { createHistory, History, createStorage, createSettings } from '../servic
|
||||||
import { create } from '../storage/local_storage_object_client';
|
import { create } from '../storage/local_storage_object_client';
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import {RequestStatusBar} from './request_status_bar';
|
import {RequestStatusBar} from './request_status_bar';
|
||||||
|
import useEventListener from '@/lib/hooks/use_event_listener';
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
selectedCluster: any;
|
selectedCluster: any;
|
||||||
saveEditorContent: (content: string)=>void;
|
saveEditorContent: (content: string)=>void;
|
||||||
initialText: string;
|
initialText: string;
|
||||||
paneKey: string;
|
paneKey: string;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_PANEL_WIDTH = 50;
|
const INITIAL_PANEL_WIDTH = 50;
|
||||||
|
@ -30,8 +32,8 @@ const ConsoleWrapper = ({
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
initialText,
|
initialText,
|
||||||
paneKey,
|
paneKey,
|
||||||
|
height,
|
||||||
}:props) => {
|
}:props) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
requestInFlight: requestInProgress,
|
requestInFlight: requestInProgress,
|
||||||
lastResult: { data: requestData, error: requestError },
|
lastResult: { data: requestData, error: requestError },
|
||||||
|
@ -53,26 +55,36 @@ const ConsoleWrapper = ({
|
||||||
const statusBarRef = useRef<HTMLDivElement>(null);
|
const statusBarRef = useRef<HTMLDivElement>(null);
|
||||||
const consoleRef = useRef<HTMLDivElement>(null);
|
const consoleRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(()=>{
|
// useEffect(()=>{
|
||||||
statusBarRef.current && consoleRef.current && (statusBarRef.current.style.width=consoleRef.current.offsetWidth+'px');
|
// const winScroll = ()=>{
|
||||||
const winScroll = ()=>{
|
// const wsTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
const wsTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
// if(wsTop>getElementTop(consoleRef.current)) {
|
||||||
if(wsTop>getElementTop(consoleRef.current)) {
|
// statusBarRef.current && (statusBarRef.current.style.position='relative');
|
||||||
statusBarRef.current && (statusBarRef.current.style.position='relative');
|
// }else{
|
||||||
}else{
|
// statusBarRef.current && (statusBarRef.current.style.position='fixed');
|
||||||
statusBarRef.current && (statusBarRef.current.style.position='fixed');
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// window.addEventListener('scroll', winScroll, {passive:true})
|
||||||
window.addEventListener('scroll', winScroll)
|
// return ()=>{
|
||||||
return ()=>{
|
// window.removeEventListener('scroll', winScroll)
|
||||||
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 (
|
return (
|
||||||
<div>
|
<div style={{height: calcHeight}}>
|
||||||
<div ref={consoleRef} className="Console">
|
<div ref={consoleRef} className="Console" style={{height:'100%'}}>
|
||||||
<PanelsContainer resizerClassName="resizer">
|
<PanelsContainer resizerClassName="resizer">
|
||||||
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }} initialWidth={INITIAL_PANEL_WIDTH}>
|
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }} initialWidth={INITIAL_PANEL_WIDTH}>
|
||||||
<ConsoleInput clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} />
|
<ConsoleInput clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} />
|
||||||
|
@ -82,12 +94,8 @@ const ConsoleWrapper = ({
|
||||||
</Panel>
|
</Panel>
|
||||||
</PanelsContainer>
|
</PanelsContainer>
|
||||||
</div>
|
</div>
|
||||||
<div ref={statusBarRef} style={{ position:'fixed', bottom:0, borderTop: '1px solid #eee', zIndex:5000}}>
|
<div ref={statusBarRef} style={{ position:'fixed', bottom:0, borderTop: '1px solid #eee', zIndex:1001, width:'100%'}}>
|
||||||
<EuiFlexGroup className="consoleContainer"
|
<div style={{background:'#fff',height:30, width:'100%'}}>
|
||||||
style={{height:30, background:'#fff'}}
|
|
||||||
gutterSize="none"
|
|
||||||
direction="column">
|
|
||||||
<EuiFlexItem className="conApp__tabsExtension">
|
|
||||||
<RequestStatusBar
|
<RequestStatusBar
|
||||||
requestInProgress={requestInProgress}
|
requestInProgress={requestInProgress}
|
||||||
selectedCluster={selectedCluster}
|
selectedCluster={selectedCluster}
|
||||||
|
@ -99,18 +107,20 @@ const ConsoleWrapper = ({
|
||||||
statusCode: lastDatum.response.statusCode,
|
statusCode: lastDatum.response.statusCode,
|
||||||
statusText: lastDatum.response.statusText,
|
statusText: lastDatum.response.statusText,
|
||||||
timeElapsedMs: lastDatum.response.timeMs,
|
timeElapsedMs: lastDatum.response.timeMs,
|
||||||
|
requestHeader: lastDatum.request.header,
|
||||||
|
responseHeader: lastDatum.response.header,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</div>
|
||||||
</EuiFlexGroup>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Console = (params:props) => {
|
const Console = (params:props) => {
|
||||||
|
|
||||||
const registryRef = useRef(new PanelRegistry());
|
const registryRef = useRef(new PanelRegistry());
|
||||||
// const [consoleInputKey] = useMemo(()=>{
|
// const [consoleInputKey] = useMemo(()=>{
|
||||||
// return [selectedCluster.id + '-console-input'];
|
// return [selectedCluster.id + '-console-input'];
|
||||||
|
|
|
@ -11,7 +11,7 @@ import './ConsoleInput.scss';
|
||||||
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es';
|
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es';
|
||||||
import { useSetInputEditor } from '../hooks/use_set_input_editor';
|
import { useSetInputEditor } from '../hooks/use_set_input_editor';
|
||||||
import '@elastic/eui/dist/eui_theme_light.css';
|
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 'antd/dist/antd.css';
|
||||||
import {retrieveAutoCompleteInfo} from '../modules/mappings/mappings';
|
import {retrieveAutoCompleteInfo} from '../modules/mappings/mappings';
|
||||||
import {useSaveCurrentTextObject} from '../hooks/use_save_current_text_object';
|
import {useSaveCurrentTextObject} from '../hooks/use_save_current_text_object';
|
||||||
|
@ -109,7 +109,6 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
|
||||||
editorInstanceRef.current = senseEditor;
|
editorInstanceRef.current = senseEditor;
|
||||||
setInputEditor(senseEditor);
|
setInputEditor(senseEditor);
|
||||||
senseEditor.paneKey = paneKey;
|
senseEditor.paneKey = paneKey;
|
||||||
editorList.addInputEditor(senseEditor);
|
|
||||||
senseEditor.update(initialText || DEFAULT_INPUT_VALUE);
|
senseEditor.update(initialText || DEFAULT_INPUT_VALUE);
|
||||||
applyCurrentSettings(senseEditor!.getCoreEditor(), {fontSize:12, wrapMode: true,});
|
applyCurrentSettings(senseEditor!.getCoreEditor(), {fontSize:12, wrapMode: true,});
|
||||||
|
|
||||||
|
@ -152,7 +151,11 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
|
||||||
},[clusterID])
|
},[clusterID])
|
||||||
|
|
||||||
const handleSaveAsCommonCommand = async () => {
|
const handleSaveAsCommonCommand = async () => {
|
||||||
const editor = registry.getInputEditor();
|
const editor = editorInstanceRef.current;
|
||||||
|
if(editor == null){
|
||||||
|
console.warn('editor is null')
|
||||||
|
return
|
||||||
|
}
|
||||||
const requests = await editor.getRequestsInRange();
|
const requests = await editor.getRequestsInRange();
|
||||||
const formattedRequest = requests.map(request => ({
|
const formattedRequest = requests.map(request => ({
|
||||||
method: request.method,
|
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 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 {
|
export interface Props {
|
||||||
requestInProgress: boolean;
|
requestInProgress: boolean;
|
||||||
|
@ -37,6 +40,8 @@ export interface Props {
|
||||||
|
|
||||||
// The time, in milliseconds, that the last request took
|
// The time, in milliseconds, that the last request took
|
||||||
timeElapsedMs: number;
|
timeElapsedMs: number;
|
||||||
|
responseHeader: string;
|
||||||
|
requestHeader: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,17 +65,103 @@ const mapStatusCodeToBadgeColor = (statusCode: number) => {
|
||||||
return 'danger';
|
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,
|
requestInProgress,
|
||||||
requestResult,
|
requestResult,
|
||||||
selectedCluster,
|
selectedCluster,
|
||||||
}) => {
|
}:Props) => {
|
||||||
let content: React.ReactNode = null;
|
let content: React.ReactNode = null;
|
||||||
const clusterContent = (<EuiFlexItem grow={false} style={{marginRight:'auto'}}>
|
const clusterContent = (<div className="base-info">
|
||||||
<EuiBadge>
|
<div className="info-item health">
|
||||||
{selectedCluster.host} - {selectedCluster.version}
|
<span>健康状态:</span>
|
||||||
</EuiBadge>
|
<i style={{position:'absolute', top: 1, right:0}}>
|
||||||
</EuiFlexItem>);
|
<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) {
|
if (requestInProgress) {
|
||||||
content = (
|
content = (
|
||||||
|
@ -85,7 +176,8 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<EuiFlexItem grow={false}>
|
<div className="status_info">
|
||||||
|
<div className="info-item">
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
position="top"
|
position="top"
|
||||||
content={
|
content={
|
||||||
|
@ -99,8 +191,8 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
||||||
{statusCode} - {statusText}
|
{statusCode} - {statusText}
|
||||||
</EuiBadge>
|
</EuiBadge>
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
</EuiFlexItem>
|
</div>
|
||||||
<EuiFlexItem grow={false}>
|
<div className="info-item">
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
position="top"
|
position="top"
|
||||||
content={
|
content={
|
||||||
|
@ -115,21 +207,48 @@ export const RequestStatusBar: FunctionComponent<Props> = ({
|
||||||
</EuiBadge>
|
</EuiBadge>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
</EuiFlexItem>
|
</div>
|
||||||
|
<div className="info-item">
|
||||||
|
<EuiText size="s">
|
||||||
|
<Button type="link" onClick={()=>{setHeaderInfoVisible(true)}}>
|
||||||
|
Headers
|
||||||
|
</Button>
|
||||||
|
</EuiText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<div className="request-status-bar">
|
||||||
justifyContent="flexEnd"
|
<div className="bar-item">{clusterContent}</div>
|
||||||
alignItems="center"
|
<div className="bar-item">{content}</div>
|
||||||
direction="row"
|
<Drawer title="Request header info"
|
||||||
gutterSize="s"
|
style={{zIndex:1004}}
|
||||||
responsive={false}
|
width={520}
|
||||||
|
|
||||||
|
destroyOnClose={true}
|
||||||
|
visible={headerInfoVisible}
|
||||||
|
onClose={()=>{setHeaderInfoVisible(false)}}
|
||||||
>
|
>
|
||||||
{clusterContent}
|
<Tabs>
|
||||||
{content}
|
<Tabs.TabPane tab="Request" key="1">
|
||||||
</EuiFlexGroup>
|
<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) {
|
setInputEditor(inputEditor: SenseEditor) {
|
||||||
this.inputEditor = inputEditor;
|
this.inputEditor = inputEditor;
|
||||||
inputEditor.setAutocompleter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputEditor() {
|
getInputEditor() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { TokensProvider } from './tokens_provider';
|
import { TokensProvider } from './tokens_provider';
|
||||||
import { Token } from './token';
|
import { Token } from './token';
|
||||||
|
import RowParser from './row_parser';
|
||||||
|
|
||||||
type MarkerRef = any;
|
type MarkerRef = any;
|
||||||
|
|
||||||
|
@ -81,6 +82,9 @@ export enum LINE_MODE {
|
||||||
* being used which is usually vendor code such as Ace or Monaco.
|
* being used which is usually vendor code such as Ace or Monaco.
|
||||||
*/
|
*/
|
||||||
export interface CoreEditor {
|
export interface CoreEditor {
|
||||||
|
getParser(): RowParser;
|
||||||
|
getAutocompleter(): AutoCompleterFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current position of the cursor.
|
* Get the current position of the cursor.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,16 +12,16 @@ export class SenseEditor {
|
||||||
currentReqRange: (Range & { markerRef: unknown }) | null;
|
currentReqRange: (Range & { markerRef: unknown }) | null;
|
||||||
parser: RowParser;
|
parser: RowParser;
|
||||||
|
|
||||||
private readonly autocomplete: ReturnType<typeof createAutocompleter>;
|
// private readonly autocomplete: ReturnType<typeof createAutocompleter>;
|
||||||
|
|
||||||
constructor(private readonly coreEditor: CoreEditor) {
|
constructor(private readonly coreEditor: CoreEditor) {
|
||||||
this.currentReqRange = null;
|
this.currentReqRange = null;
|
||||||
this.parser = new RowParser(this.coreEditor);
|
// this.parser = new RowParser(this.coreEditor);
|
||||||
this.autocomplete = createAutocompleter({
|
// this.autocomplete = createAutocompleter({
|
||||||
coreEditor,
|
// coreEditor,
|
||||||
parser: this.parser,
|
// });
|
||||||
});
|
// this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
||||||
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
this.parser = coreEditor.getParser();
|
||||||
this.coreEditor.on(
|
this.coreEditor.on(
|
||||||
'tokenizerUpdate',
|
'tokenizerUpdate',
|
||||||
this.highlightCurrentRequestsAndUpdateActionBar.bind(this)
|
this.highlightCurrentRequestsAndUpdateActionBar.bind(this)
|
||||||
|
@ -30,10 +30,6 @@ export class SenseEditor {
|
||||||
this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this));
|
this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
setAutocompleter = ()=>{
|
|
||||||
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevRequestStart = (rowOrPos?: number | Position): Position => {
|
prevRequestStart = (rowOrPos?: number | Position): Position => {
|
||||||
let curRow: number;
|
let curRow: number;
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { instance as registry } from '../../contexts/editor_context/editor_regis
|
||||||
import { useRequestActionContext } from '../../contexts/request_context';
|
import { useRequestActionContext } from '../../contexts/request_context';
|
||||||
import { useServicesContext } from '../../contexts/services_context';
|
import { useServicesContext } from '../../contexts/services_context';
|
||||||
import {getCommand} from '../../modules/mappings/mappings';
|
import {getCommand} from '../../modules/mappings/mappings';
|
||||||
|
import {useEditorReadContext} from '../../contexts/editor_context';
|
||||||
|
|
||||||
function buildRawCommonCommandRequest(cmd:any){
|
function buildRawCommonCommandRequest(cmd:any){
|
||||||
const {requests} = cmd._source;
|
const {requests} = cmd._source;
|
||||||
|
@ -48,10 +49,12 @@ function buildRawCommonCommandRequest(cmd:any){
|
||||||
export const useSendCurrentRequestToES = () => {
|
export const useSendCurrentRequestToES = () => {
|
||||||
const dispatch = useRequestActionContext();
|
const dispatch = useRequestActionContext();
|
||||||
const { services: { history }, clusterID } = useServicesContext();
|
const { services: { history }, clusterID } = useServicesContext();
|
||||||
|
const {sensorEditor:editor} = useEditorReadContext();
|
||||||
|
|
||||||
return useCallback(async () => {
|
return useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const editor = registry.getInputEditor();
|
// const editor = registry.getInputEditor();
|
||||||
|
if(!editor) return
|
||||||
const requests = await editor.getRequestsInRange();
|
const requests = await editor.getRequestsInRange();
|
||||||
if (!requests.length) {
|
if (!requests.length) {
|
||||||
console.log('No request selected. Select a request by placing the cursor inside it.');
|
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) {
|
if (reqId !== CURRENT_REQ_ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
|
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
|
||||||
|
|
||||||
const isSuccess =
|
const isSuccess =
|
||||||
|
@ -83,7 +82,10 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
||||||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
||||||
|
|
||||||
if (isSuccess) {
|
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');
|
const warnings = xhr.getResponseHeader('warning');
|
||||||
if (warnings) {
|
if (warnings) {
|
||||||
|
@ -102,11 +104,13 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
||||||
statusText: xhr.statusText,
|
statusText: xhr.statusText,
|
||||||
contentType: xhr.getResponseHeader('Content-Type'),
|
contentType: xhr.getResponseHeader('Content-Type'),
|
||||||
value,
|
value,
|
||||||
|
header: resObj.response_header,
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
data: esData,
|
data: esData,
|
||||||
method: esMethod,
|
method: esMethod,
|
||||||
path: esPath,
|
path: esPath,
|
||||||
|
header: resObj.request_header,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,8 +120,15 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
||||||
let value;
|
let value;
|
||||||
let contentType: string;
|
let contentType: string;
|
||||||
if (xhr.responseText) {
|
if (xhr.responseText) {
|
||||||
value = xhr.responseText; // ES error should be shown
|
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');
|
contentType = xhr.getResponseHeader('Content-Type');
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
value = 'Request failed to get to the server (status code: ' + xhr.status + ')';
|
value = 'Request failed to get to the server (status code: ' + xhr.status + ')';
|
||||||
contentType = 'text/plain';
|
contentType = 'text/plain';
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const useSetInputEditor = () => {
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(editor: SenseEditor) => {
|
(editor: SenseEditor) => {
|
||||||
dispatch({ type: 'setInputEditor', payload: editor });
|
dispatch({ type: 'setInputEditor', payload: editor });
|
||||||
registry.setInputEditor(editor);
|
// registry.setInputEditor(editor);
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
|
@ -360,13 +360,10 @@ function addMetaToTermsList(
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
export default function ({
|
export default function ({
|
||||||
coreEditor: editor,
|
coreEditor: editor,
|
||||||
parser,
|
|
||||||
}: {
|
}: {
|
||||||
coreEditor: CoreEditor;
|
coreEditor: CoreEditor;
|
||||||
parser: RowParser;
|
|
||||||
}) {
|
}) {
|
||||||
|
const parser = new RowParser(editor)
|
||||||
|
|
||||||
function applyTerm(term: {
|
function applyTerm(term: {
|
||||||
value?: string;
|
value?: string;
|
||||||
context?: AutoCompleteContext;
|
context?: AutoCompleteContext;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { stringify } from 'query-string';
|
import { stringify } from 'query-string';
|
||||||
|
import {pathPrefix} from '@/services/common';
|
||||||
|
|
||||||
interface SendOptions {
|
interface SendOptions {
|
||||||
asSystemRequest?: boolean;
|
asSystemRequest?: boolean;
|
||||||
|
@ -103,12 +104,7 @@ export function send(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryCommonCommands(title?: string) {
|
export function queryCommonCommands(title?: string) {
|
||||||
const clusterID = extractClusterIDFromURL();
|
let url = `${pathPrefix}/elasticsearch/command`;
|
||||||
if(!clusterID){
|
|
||||||
console.log('can not get clusterid from url');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let url = `/elasticsearch/${clusterID}/command/_search`;
|
|
||||||
if(title){
|
if(title){
|
||||||
url +=`?title=${title}`
|
url +=`?title=${title}`
|
||||||
}
|
}
|
||||||
|
@ -124,12 +120,7 @@ export function constructESUrl(baseUri: string, path: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveCommonCommand(params: any) {
|
export function saveCommonCommand(params: any) {
|
||||||
const clusterID = extractClusterIDFromURL();
|
return fetch(`${pathPrefix}/elasticsearch/command`, {
|
||||||
if(!clusterID){
|
|
||||||
console.log('can not get clusterid from url');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return fetch(`/elasticsearch/${clusterID}/command`, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
headers:{
|
headers:{
|
||||||
|
|
|
@ -10,6 +10,10 @@ import { EditorEvent, AutoCompleterFunction } from '../../entities/core_editor';
|
||||||
import { AceTokensProvider } from '../../entities/ace_tokens_providers';
|
import { AceTokensProvider } from '../../entities/ace_tokens_providers';
|
||||||
import * as curl from './curl';
|
import * as curl from './curl';
|
||||||
import smartResize from './smart_resize';
|
import smartResize from './smart_resize';
|
||||||
|
import createAutocompleter from '../../modules/autocomplete/autocomplete';
|
||||||
|
import RowParser from '../../entities/row_parser';
|
||||||
|
|
||||||
|
(function initAceEditor() {
|
||||||
ace.define(
|
ace.define(
|
||||||
'ace/autocomplete/text_completer',
|
'ace/autocomplete/text_completer',
|
||||||
['require', 'exports', 'module'],
|
['require', 'exports', 'module'],
|
||||||
|
@ -33,6 +37,32 @@ ace.define(
|
||||||
|
|
||||||
const langTools = ace.acequire('ace/ext/language_tools');
|
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
|
// @ts-ignore
|
||||||
import * as InputMode from './mode/input';
|
import * as InputMode from './mode/input';
|
||||||
const _AceRange = ace.acequire('ace/range').Range;
|
const _AceRange = ace.acequire('ace/range').Range;
|
||||||
|
@ -45,10 +75,16 @@ export class LegacyCoreEditor implements CoreEditor {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
$actions: JQuery<HTMLElement>;
|
$actions: JQuery<HTMLElement>;
|
||||||
resize: () => void;
|
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.$actions = $(actions);
|
||||||
this.editor.setShowPrintMargin(false);
|
this.editor.setShowPrintMargin(false);
|
||||||
|
this.parser = new RowParser(this);
|
||||||
|
this.autocompleter = createAutocompleter({
|
||||||
|
coreEditor: this,
|
||||||
|
}).getCompletions
|
||||||
|
|
||||||
const session = this.editor.getSession();
|
const session = this.editor.getSession();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -72,6 +108,14 @@ export class LegacyCoreEditor implements CoreEditor {
|
||||||
this.editor.$blockScrolling = Infinity;
|
this.editor.$blockScrolling = Infinity;
|
||||||
this.hideActionsBar();
|
this.hideActionsBar();
|
||||||
this.editor.focus();
|
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
|
// dirty check for tokenizer state, uses a lot less cycles
|
||||||
|
@ -388,6 +432,7 @@ export class LegacyCoreEditor implements CoreEditor {
|
||||||
prefix: string,
|
prefix: string,
|
||||||
callback: (...args: unknown[]) => void
|
callback: (...args: unknown[]) => void
|
||||||
) => {
|
) => {
|
||||||
|
debugger
|
||||||
const position: Position = {
|
const position: Position = {
|
||||||
lineNumber: pos.row + 1,
|
lineNumber: pos.row + 1,
|
||||||
column: pos.column + 1,
|
column: pos.column + 1,
|
||||||
|
|
|
@ -223,15 +223,23 @@ function getFieldNamesFromProperties(properties = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTemplates(templatesObject = {}, clusterID) {
|
function loadTemplates(templatesObject = {}, clusterID) {
|
||||||
|
templatesObject = getRawBody(templatesObject);
|
||||||
templates[clusterID] = Object.keys(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) {
|
export function loadMappings(mappings, clusterID) {
|
||||||
|
mappings = getRawBody(mappings)
|
||||||
let clusterPerIndexTypes = {};
|
let clusterPerIndexTypes = {};
|
||||||
|
|
||||||
$.each(mappings, function (index, indexMapping) {
|
$.each(mappings, function (index, indexMapping) {
|
||||||
const normalizedIndexMappings = {};
|
const normalizedIndexMappings = {};
|
||||||
|
|
||||||
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
||||||
if (indexMapping.mappings && _.keys(indexMapping).length === 1) {
|
if (indexMapping.mappings && _.keys(indexMapping).length === 1) {
|
||||||
indexMapping = indexMapping.mappings;
|
indexMapping = indexMapping.mappings;
|
||||||
|
@ -242,7 +250,7 @@ export function loadMappings(mappings, clusterID) {
|
||||||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||||
normalizedIndexMappings[typeName] = fieldList;
|
normalizedIndexMappings[typeName] = fieldList;
|
||||||
} else {
|
} else {
|
||||||
normalizedIndexMappings[typeName] = [];
|
normalizedIndexMappings[typeName] = getFieldNamesFromProperties(typeMapping.properties); // for es 2.x, 5.x, 6.x
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
clusterPerIndexTypes[index] = normalizedIndexMappings;
|
clusterPerIndexTypes[index] = normalizedIndexMappings;
|
||||||
|
@ -251,6 +259,7 @@ export function loadMappings(mappings, clusterID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadAliases(aliases, clusterID) {
|
export function loadAliases(aliases, clusterID) {
|
||||||
|
aliases = getRawBody(aliases)
|
||||||
let clusterPerAliasIndexes = {};
|
let clusterPerAliasIndexes = {};
|
||||||
$.each(aliases || {}, function (index, omdexAliases) {
|
$.each(aliases || {}, function (index, omdexAliases) {
|
||||||
// verify we have an index defined. useful when mapping loading is disabled
|
// 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 {
|
export interface Store {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
currentTextObject: TextObject | null;
|
currentTextObject: TextObject | null;
|
||||||
|
sensorEditor: SenseEditor | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialValue: Store = produce<Store>(
|
export const initialValue: Store = produce<Store>(
|
||||||
{
|
{
|
||||||
ready: false,
|
ready: false,
|
||||||
currentTextObject: null,
|
currentTextObject: null,
|
||||||
|
sensorEditor: null,
|
||||||
},
|
},
|
||||||
identity
|
identity
|
||||||
);
|
);
|
||||||
|
@ -58,6 +60,7 @@ export const reducer: Reducer<Store, Action> = (state, action) =>
|
||||||
if (action.type === 'setInputEditor') {
|
if (action.type === 'setInputEditor') {
|
||||||
if (action.payload) {
|
if (action.payload) {
|
||||||
draft.ready = true;
|
draft.ready = true;
|
||||||
|
draft.sensorEditor = action.payload;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ function useStorage(key, defaultValue, storageObject, encryptor) {
|
||||||
return storeValue
|
return storeValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof initialValue === "function") {
|
if (typeof defaultValue === "function") {
|
||||||
return defaultValue()
|
return defaultValue()
|
||||||
} else {
|
} else {
|
||||||
return defaultValue
|
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 {formatESSearchResult, extractClusterIDFromURL} from '@/lib/elasticsearch/util';
|
||||||
import {Modal} from 'antd';
|
import {Modal} from 'antd';
|
||||||
import router from "umi/router";
|
import router from "umi/router";
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const MENU_COLLAPSED_KEY = "search-center:menu:collapsed";
|
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);
|
let res = yield call(getClusterStatus, payload);
|
||||||
if(!res){
|
if(!res){
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
const {clusterStatus} = yield select(state=>state.global);
|
||||||
if(res.error){
|
if(res.error){
|
||||||
console.log(res.error)
|
console.log(res.error)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(!_.isEqual(res, clusterStatus)){
|
||||||
yield put({
|
yield put({
|
||||||
type: 'saveData',
|
type: 'saveData',
|
||||||
payload: {
|
payload: {
|
||||||
clusterStatus: res
|
clusterStatus: res
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -251,7 +255,7 @@ export default {
|
||||||
// Subscribe history(url) change, trigger `load` action if pathname is `/`
|
// Subscribe history(url) change, trigger `load` action if pathname is `/`
|
||||||
return history.listen(({ pathname, search }) => {
|
return history.listen(({ pathname, search }) => {
|
||||||
let clusterVisible = true;
|
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))){
|
if(clusterHiddenPath.some(p=>pathname.startsWith(p))){
|
||||||
clusterVisible = false;
|
clusterVisible = false;
|
||||||
if(pathname.includes("elasticsearch")){
|
if(pathname.includes("elasticsearch")){
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import Console from '../../components/kibana/console/components/Console';
|
import Console from '../../components/kibana/console/components/Console';
|
||||||
import {connect} from 'dva';
|
import {connect} from 'dva';
|
||||||
import {Tabs, Button, Icon, Menu, Dropdown} from 'antd';
|
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 {useLocalStorage} from '@/lib/hooks/storage';
|
||||||
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings';
|
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;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
|
@ -25,7 +28,7 @@ const addTab = (state: any, action: any) => {
|
||||||
const { panes } = state;
|
const { panes } = state;
|
||||||
const {cluster} = action.payload;
|
const {cluster} = action.payload;
|
||||||
const activeKey = `${cluster.id}:${new Date().valueOf()}`;
|
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 {
|
return {
|
||||||
...state,
|
...state,
|
||||||
panes,
|
panes,
|
||||||
|
@ -58,7 +61,22 @@ const consoleTabReducer = (state: any, action: any) => {
|
||||||
...state,
|
...state,
|
||||||
activeKey: payload.activeKey,
|
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;
|
break;
|
||||||
case 'saveContent':
|
case 'saveContent':
|
||||||
const panes = state.panes.map((pane)=>{
|
const panes = state.panes.map((pane)=>{
|
||||||
|
@ -81,24 +99,59 @@ const consoleTabReducer = (state: any, action: any) => {
|
||||||
return newState;
|
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(()=>{
|
const clusterMap = useMemo(()=>{
|
||||||
let cm = {};
|
let cm = {};
|
||||||
|
if(!clusterStatus){
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
(clusterList || []).map((cluster: any)=>{
|
(clusterList || []).map((cluster: any)=>{
|
||||||
|
cluster.status = clusterStatus[cluster.id].health?.status;
|
||||||
|
if(!clusterStatus[cluster.id].available){
|
||||||
|
cluster.status = 'unavailable';
|
||||||
|
}
|
||||||
cm[cluster.id] = cluster;
|
cm[cluster.id] = cluster;
|
||||||
});
|
});
|
||||||
return cm;
|
return cm;
|
||||||
}, [clusterList])
|
}, [clusterList, clusterStatus])
|
||||||
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", {
|
const initialDefaultState = ()=>{
|
||||||
panes: [],
|
const defaultActiveKey = `${selectedCluster.id}:${new Date().valueOf()}`;
|
||||||
activeKey: '',
|
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,
|
encode: JSON.stringify,
|
||||||
decode: JSON.parse,
|
decode: JSON.parse,
|
||||||
})
|
})
|
||||||
const [tabState, dispatch] = useReducer(consoleTabReducer, localState)
|
const [tabState, dispatch] = useReducer(consoleTabReducer, localState)
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
if(tabState.panes.length == 0){
|
||||||
|
removeLocalState()
|
||||||
|
return
|
||||||
|
}
|
||||||
setLocalState(tabState)
|
setLocalState(tabState)
|
||||||
}, [tabState])
|
}, [tabState])
|
||||||
|
|
||||||
|
@ -151,19 +204,95 @@ const ConsoleUI = ({clusterList}: any)=>{
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabBarExtra =(<Dropdown overlay={menu}>
|
const rootRef = useRef(null);
|
||||||
<Button size="small">
|
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 =(
|
||||||
|
<div>
|
||||||
|
<Dropdown overlay={menu}>
|
||||||
|
<Button size="small" style={{marginRight:5}}>
|
||||||
<Icon type="plus"/>
|
<Icon type="plus"/>
|
||||||
</Button>
|
</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]);
|
setClusterID(tabState.activeKey?.split(':')[0]);
|
||||||
const panes = tabState.panes.filter((pane: any)=>{
|
const panes = tabState.panes.filter((pane: any)=>{
|
||||||
return typeof clusterMap[pane.cluster_id] != 'undefined';
|
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 (
|
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
|
<Tabs
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
activeKey={tabState.activeKey}
|
activeKey={tabState.activeKey}
|
||||||
|
@ -173,13 +302,14 @@ const ConsoleUI = ({clusterList}: any)=>{
|
||||||
tabBarExtraContent={tabBarExtra}
|
tabBarExtraContent={tabBarExtra}
|
||||||
>
|
>
|
||||||
{panes.map(pane => (
|
{panes.map(pane => (
|
||||||
<TabPane tab={clusterMap[pane.cluster_id].name} key={pane.key} closable={pane.closable}>
|
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
||||||
<TabConsole selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
<TabConsole height={editorHeight - 40} selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
||||||
{/* {pane.content} */}
|
{/* {pane.content} */}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
</Resizable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,4 +318,6 @@ export default connect(({
|
||||||
})=>({
|
})=>({
|
||||||
selectedCluster: global.selectedCluster,
|
selectedCluster: global.selectedCluster,
|
||||||
clusterList: global.clusterList,
|
clusterList: global.clusterList,
|
||||||
|
clusterStatus: global.clusterStatus,
|
||||||
|
height: window.innerHeight - 75 + 'px',
|
||||||
}))(ConsoleUI);
|
}))(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