cluster and command pagination and new metrics

This commit is contained in:
liugq 2021-11-26 16:23:45 +08:00
parent 2af1c743d4
commit 94dfeb9217
18 changed files with 544 additions and 346 deletions

View File

@ -1,9 +1,7 @@
package api
import (
log "github.com/cihub/seelog"
"infini.sh/framework/core/api"
"infini.sh/framework/core/task"
"infini.sh/search-center/api/index_management"
"infini.sh/search-center/config"
"infini.sh/search-center/service/alerting"
@ -79,14 +77,14 @@ func Init(cfg *config.AppConfig) {
api.HandleAPIMethod(api.POST, "/elasticsearch/:id/alerting/_monitors/:monitorID/_acknowledge/alerts", alerting.AcknowledgeAlerts)
task.RegisterScheduleTask(task.ScheduleTask{
Description: "sync reindex task result",
Task: func() {
err := index_management.SyncRebuildResult(cfg.Elasticsearch)
if err != nil {
log.Error(err)
}
},
})
//task.RegisterScheduleTask(task.ScheduleTask{
// Description: "sync reindex task result",
// Task: func() {
// err := index_management.SyncRebuildResult(cfg.Elasticsearch)
// if err != nil {
// log.Error(err)
// }
// },
//})
}

View File

@ -3,10 +3,14 @@ elasticsearch:
- name: default
enabled: true
monitored: false
endpoint: http://192.168.3.188:9299
# endpoint: https://k8es.client.bindiego.com
# basic_auth:
# username: infini
# password: BTxSrT5m33rfpF4
endpoint: http://localhost:9200
basic_auth:
username: elastic
password: ZBdkVQUUdF1Sir4X4BGB
password: infinilabs
#前端 UI HTTP 配置
web:

View File

@ -1,32 +1,46 @@
// @ts-ignore
import React, { useRef, useMemo,useEffect, useLayoutEffect, useState } from 'react';
import ConsoleInput from './ConsoleInput';
import ConsoleOutput from './ConsoleOutput';
import { Panel } from './Panel';
import PanelsContainer from './PanelContainer';
import { PanelContextProvider } from '../contexts/panel_context';
import { PanelRegistry } from '../contexts/panel_context/registry';
import './Console.scss';
import { RequestContextProvider, useRequestReadContext } from '../contexts/request_context';
import {EditorContextProvider} from '../contexts/editor_context/editor_context';
import { ServicesContextProvider } from '../contexts/services_context';
import { createHistory, History, createStorage, createSettings } from '../services';
import { create } from '../storage/local_storage_object_client';
import { EuiFlexGroup, EuiFlexItem,EuiCodeBlock } from '@elastic/eui';
import {RequestStatusBar} from './request_status_bar';
import useEventListener from '@/lib/hooks/use_event_listener';
import {Tabs} from 'antd';
import React, {
useRef,
useMemo,
useEffect,
useLayoutEffect,
useState,
} from "react";
import ConsoleInput from "./ConsoleInput";
import ConsoleOutput from "./ConsoleOutput";
import { Panel } from "./Panel";
import PanelsContainer from "./PanelContainer";
import { PanelContextProvider } from "../contexts/panel_context";
import { PanelRegistry } from "../contexts/panel_context/registry";
import "./Console.scss";
import {
RequestContextProvider,
useRequestReadContext,
} from "../contexts/request_context";
import { EditorContextProvider } from "../contexts/editor_context/editor_context";
import { ServicesContextProvider } from "../contexts/services_context";
import {
createHistory,
History,
createStorage,
createSettings,
} from "../services";
import { create } from "../storage/local_storage_object_client";
import { EuiFlexGroup, EuiFlexItem, EuiCodeBlock } from "@elastic/eui";
import { RequestStatusBar } from "./request_status_bar";
import useEventListener from "@/lib/hooks/use_event_listener";
import { Tabs } from "antd";
interface props {
selectedCluster: any;
saveEditorContent: (content: string)=>void;
saveEditorContent: (content: string) => void;
initialText: string;
paneKey: string;
height: number;
}
const INITIAL_PANEL_WIDTH = 50;
const PANEL_MIN_WIDTH = '300px';
const PANEL_MIN_WIDTH = "300px";
const ConsoleWrapper = ({
selectedCluster,
@ -34,7 +48,7 @@ const ConsoleWrapper = ({
initialText,
paneKey,
height,
}:props) => {
}: props) => {
const {
requestInFlight: requestInProgress,
lastResult: { data: requestData, error: requestError },
@ -42,7 +56,7 @@ const ConsoleWrapper = ({
const lastDatum = requestData?.[requestData.length - 1] ?? requestError;
const calcHeight = height > 0 ? (height)+'px' : '100%';
const calcHeight = height > 0 ? height + "px" : "100%";
// const leftBarRef = useRef(null)
// const rightBarRef = useRef(null)
// const [widths, setWidths] = useState(['calc(50% - 7px)', 'calc(50% - 7px)'])
@ -51,14 +65,39 @@ const ConsoleWrapper = ({
// setWidths([lp+'%', rp+'%']);
// }
return (
<div style={{height: calcHeight}}>
<div className="Console" style={{height:'100%'}}>
<PanelsContainer resizerClassName="resizer">
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:26 }} initialWidth={INITIAL_PANEL_WIDTH}>
<ConsoleInput height={height-26+'px'} clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} />
<div style={{background:'#fff', position:'absolute', left:0, bottom:0, width: '100%', height:26, zIndex:997, borderTop: '1px solid #eee'}}>
<RequestStatusBar
return (
<div style={{ height: calcHeight }}>
<div className="Console" style={{ height: "100%" }}>
<PanelsContainer resizerClassName="resizer">
<Panel
style={{
height: "100%",
position: "relative",
minWidth: PANEL_MIN_WIDTH,
paddingBottom: 26,
}}
initialWidth={INITIAL_PANEL_WIDTH}
>
<ConsoleInput
height={height - 26 + "px"}
clusterID={selectedCluster.id}
saveEditorContent={saveEditorContent}
initialText={initialText}
paneKey={paneKey}
/>
<div
style={{
background: "#fff",
position: "absolute",
left: 0,
bottom: 0,
width: "100%",
height: 26,
zIndex: 997,
borderTop: "1px solid #eee",
}}
>
<RequestStatusBar
requestInProgress={requestInProgress}
selectedCluster={selectedCluster}
left={true}
@ -69,40 +108,62 @@ const ConsoleWrapper = ({
endpoint: lastDatum.request.path,
statusCode: lastDatum.response.statusCode,
statusText: lastDatum.response.statusText,
timeElapsedMs: lastDatum.response.timeMs,
requestHeader: lastDatum.request.header,
responseHeader: lastDatum.response.header,
}
: undefined
}
/>
</div>
</Panel>
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:26 }} initialWidth={INITIAL_PANEL_WIDTH}>
<Tabs tabPosition='right' style={{height:'100%', width:'100%'}} size="small">
<Tabs.TabPane tab="Result" key="result">
<div style={{height:height-26}}>
<ConsoleOutput clusterID={selectedCluster.id} />
</div>
</Tabs.TabPane>
<Tabs.TabPane tab="Headers" key="headers">
<Tabs animated={false}>
<Tabs.TabPane tab="Request" key="1">
<EuiCodeBlock language="text" isCopyable paddingSize="s">
{lastDatum?.request.header}
</EuiCodeBlock>
</Tabs.TabPane>
<Tabs.TabPane tab="Response" key="2">
<EuiCodeBlock language="text" isCopyable paddingSize="s">
{lastDatum?.response.header}
</EuiCodeBlock>
</Tabs.TabPane>
</Tabs>
</Tabs.TabPane>
</Tabs>
<div style={{background:'#fff', position:'absolute', right:0, bottom:0, width: '100%', height:26, zIndex:997, borderTop: '1px solid #eee'}}>
<RequestStatusBar
timeElapsedMs: lastDatum.response.timeMs,
requestHeader: lastDatum.request.header,
responseHeader: lastDatum.response.header,
}
: undefined
}
/>
</div>
</Panel>
<Panel
style={{
height: "100%",
position: "relative",
minWidth: PANEL_MIN_WIDTH,
paddingBottom: 26,
}}
initialWidth={INITIAL_PANEL_WIDTH}
>
<Tabs
tabPosition="right"
style={{ height: "100%", width: "100%" }}
size="small"
>
<Tabs.TabPane tab="Result" key="result">
<div style={{ height: height - 26 }}>
<ConsoleOutput clusterID={selectedCluster.id} />
</div>
</Tabs.TabPane>
<Tabs.TabPane tab="Headers" key="headers">
<Tabs animated={false}>
<Tabs.TabPane tab="Request" key="1">
<EuiCodeBlock language="text" isCopyable paddingSize="s">
{lastDatum?.request.header}
</EuiCodeBlock>
</Tabs.TabPane>
<Tabs.TabPane tab="Response" key="2">
<EuiCodeBlock language="text" isCopyable paddingSize="s">
{lastDatum?.response.header}
</EuiCodeBlock>
</Tabs.TabPane>
</Tabs>
</Tabs.TabPane>
</Tabs>
<div
style={{
background: "#fff",
position: "absolute",
right: 0,
bottom: 0,
width: "100%",
height: 26,
zIndex: 997,
borderTop: "1px solid #eee",
}}
>
<RequestStatusBar
requestInProgress={requestInProgress}
selectedCluster={selectedCluster}
requestResult={
@ -112,18 +173,18 @@ const ConsoleWrapper = ({
endpoint: lastDatum.request.path,
statusCode: lastDatum.response.statusCode,
statusText: lastDatum.response.statusText,
timeElapsedMs: lastDatum.response.timeMs,
requestHeader: lastDatum.request.header,
responseHeader: lastDatum.response.header,
}
: undefined
}
/>
</div>
</Panel>
</PanelsContainer>
</div>
{/* <div ref={statusBarRef} style={{ position:'fixed', bottom:0, borderTop: '1px solid #eee', zIndex:1001, width:'100%'}}>
timeElapsedMs: lastDatum.response.timeMs,
requestHeader: lastDatum.request.header,
responseHeader: lastDatum.response.header,
}
: undefined
}
/>
</div>
</Panel>
</PanelsContainer>
</div>
{/* <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}
@ -145,38 +206,41 @@ const ConsoleWrapper = ({
/>
</div>
</div> */}
</div>
</div>
);
};
const Console = (params:props) => {
const Console = (params: props) => {
const registryRef = useRef(new PanelRegistry());
// const [consoleInputKey] = useMemo(()=>{
// return [selectedCluster.id + '-console-input'];
// },[selectedCluster])
const {storage, history, objectStorageClient, settings} = useMemo(()=>{
const { storage, history, objectStorageClient, settings } = useMemo(() => {
const storage = createStorage({
engine: window.localStorage,
prefix: 'sense:',
prefix: "sense:",
});
const history: History = createHistory({ storage });
const objectStorageClient = create(storage);
const settings = createSettings({storage});
return {storage, history, objectStorageClient, settings};
}, [])
return ( <PanelContextProvider registry={registryRef.current}>
<RequestContextProvider>
<EditorContextProvider>
<ServicesContextProvider value={{ services: { history, storage, objectStorageClient, settings}, clusterID: params.selectedCluster.id }}>
const settings = createSettings({ storage });
return { storage, history, objectStorageClient, settings };
}, []);
return (
<PanelContextProvider registry={registryRef.current}>
<RequestContextProvider>
<EditorContextProvider>
<ServicesContextProvider
value={{
services: { history, storage, objectStorageClient, settings },
clusterID: params.selectedCluster.id,
}}
>
<ConsoleWrapper {...params} />
</ServicesContextProvider>
</EditorContextProvider>
</EditorContextProvider>
</RequestContextProvider>
</PanelContextProvider>
);
}
);
};
export default Console;

View File

@ -166,8 +166,10 @@ const ConsoleInputUI = ({
};
}, []);
useEffect(() => {
retrieveAutoCompleteInfo(settings, settings.getAutocomplete(), clusterID);
aceEditorRef.current && (aceEditorRef.current["clusterID"] = clusterID);
if (clusterID) {
retrieveAutoCompleteInfo(settings, settings.getAutocomplete(), clusterID);
aceEditorRef.current && (aceEditorRef.current["clusterID"] = clusterID);
}
}, [clusterID]);
const handleSaveAsCommonCommand = async () => {

View File

@ -30,58 +30,64 @@
* GitHub history for details.
*/
import { useCallback } from 'react';
import { sendRequestToES } from './send_request_to_es';
import { instance as registry } from '../../contexts/editor_context/editor_registry';
import { useRequestActionContext } from '../../contexts/request_context';
import { useServicesContext } from '../../contexts/services_context';
import {getCommand} from '../../modules/mappings/mappings';
import {useEditorReadContext} from '../../contexts/editor_context';
import { useCallback } from "react";
import { sendRequestToES } from "./send_request_to_es";
import { instance as registry } from "../../contexts/editor_context/editor_registry";
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;
const strReqs = requests.map((req: any)=>{
const {method, path, body} = req;
function buildRawCommonCommandRequest(cmd: any) {
const { requests } = cmd._source;
const strReqs = requests.map((req: any) => {
const { method, path, body } = req;
return `${method} ${path}\n${body}`;
})
return strReqs.join('\n');
});
return strReqs.join("\n");
}
export const useSendCurrentRequestToES = () => {
const dispatch = useRequestActionContext();
const { services: { history }, clusterID } = useServicesContext();
const {sensorEditor:editor} = useEditorReadContext();
const {
services: { history },
clusterID,
} = useServicesContext();
const { sensorEditor: editor } = useEditorReadContext();
return useCallback(async () => {
try {
// const editor = registry.getInputEditor();
if(!editor) return
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.');
console.log(
"No request selected. Select a request by placing the cursor inside it."
);
return;
}
const {url, method, data} = requests[0];
if(method === 'LOAD'){
const rawUrl = data[0]? data[0].slice(4).trim(): url;
const { url, method, data } = requests[0];
if (method === "LOAD") {
const rawUrl = data[0] ? data[0].slice(4).trim() : url;
const cmd = getCommand(rawUrl);
// const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition());
const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber;
let crange = await editor.getRequestRange(lineNumber)
const rawRequest = buildRawCommonCommandRequest(cmd)
// const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition());
const lineNumber = editor.getCoreEditor().getCurrentPosition()
.lineNumber;
let crange = await editor.getRequestRange(lineNumber);
const rawRequest = buildRawCommonCommandRequest(cmd);
await editor.getCoreEditor().replaceRange(crange as any, rawRequest);
// await editor.autoIndent();
// editor.getCoreEditor().getContainer().focus();
// crange = await editor.getRequestRange(lineNumber)
// editor.getCoreEditor().moveCursorToPosition({
// ...crange?.end as any,
// // column: editor.getCoreEditor().getLineValue(lineNumber).length + 1,
// });
return;
}
dispatch({ type: 'sendRequest', payload: undefined });
dispatch({ type: "sendRequest", payload: undefined });
// @ts-ignore
const results = await sendRequestToES({ requests, clusterID });
@ -104,20 +110,20 @@ export const useSendCurrentRequestToES = () => {
// }
//
dispatch({
type: 'requestSuccess',
type: "requestSuccess",
payload: {
data: results,
},
});
} catch (e) {
if (e?.response) {
if (e) {
dispatch({
type: 'requestFail',
payload: e,
type: "requestSuccess",
payload: { data: [e] },
});
} else {
dispatch({
type: 'requestFail',
type: "requestFail",
payload: undefined,
});
}

View File

@ -31,10 +31,10 @@
*/
// @ts-ignore
import $ from 'jquery';
import $ from "jquery";
// @ts-ignore
import { stringify } from 'query-string';
import {pathPrefix, ESPrefix} from '@/services/common';
import { stringify } from "query-string";
import { pathPrefix, ESPrefix } from "@/services/common";
interface SendOptions {
asSystemRequest?: boolean;
@ -48,14 +48,14 @@ export function getVersion() {
}
export function getContentType(body: unknown) {
if (!body) return 'text/plain';
return 'application/json';
if (!body) return "text/plain";
return "application/json";
}
export function extractClusterIDFromURL(){
export function extractClusterIDFromURL() {
const matchs = location.hash.match(/\/elasticsearch\/(\w+)\/?/);
if(!matchs){
return ''
if (!matchs) {
return "";
}
return matchs[1];
}
@ -84,16 +84,20 @@ export function send(
data,
contentType: getContentType(data),
cache: false,
crossDomain: true,
type: 'POST',
dataType: 'json', // disable automatic guessing
// crossDomain: true,
type: "POST",
dataType: "json", // disable automatic guessing
};
$.ajax(options).then(
(responseData: any, textStatus: string, jqXHR: unknown) => {
wrappedDfd.resolveWith({}, [responseData, textStatus, jqXHR]);
},
((jqXHR: { status: number; responseText: string }, textStatus: string, errorThrown: Error) => {
((
jqXHR: { status: number; responseText: string },
textStatus: string,
errorThrown: Error
) => {
if (jqXHR.status === 0) {
jqXHR.responseText =
"\n\nFailed to connect to Console's backend.\nPlease check the server is up and running";
@ -106,26 +110,26 @@ export function send(
export function queryCommonCommands(title?: string) {
let url = `${pathPrefix}/elasticsearch/command`;
if(title){
url +=`?title=${title}`
if (title) {
url += `?title=${title}`;
}
return fetch(url, {
method: 'GET',
})
method: "GET",
});
}
export function constructESUrl(baseUri: string, path: string) {
baseUri = baseUri.replace(/\/+$/, '');
path = path.replace(/^\/+/, '');
return baseUri + '/' + path;
baseUri = baseUri.replace(/\/+$/, "");
path = path.replace(/^\/+/, "");
return baseUri + "/" + path;
}
export function saveCommonCommand(params: any) {
return fetch(`${pathPrefix}/elasticsearch/command`, {
method: 'POST',
method: "POST",
body: JSON.stringify(params),
headers:{
'content-type': 'application/json'
}
})
}
headers: {
"content-type": "application/json",
},
});
}

View File

@ -207,8 +207,12 @@ export default {
"Storage Usage",
"dashboard.charts.title.cluster_storage.axis.available_storage":
"Storage Available",
"dashboard.charts.title.cluster_documents.axis.documents": "Documents Count",
"dashboard.charts.title.cluster_documents.axis.count": "Documents Count",
"dashboard.charts.title.cluster_documents.axis.deleted": "Docmuents Deleted",
"dashboard.charts.title.cluster_indices.axis.count": "Indices Count",
"dashboard.charts.title.cluster_documents.axis.counts": "Shards Count",
"dashboard.charts.title.node_count.axis.count": "Nodes Count",
"dashboard.charts.title.cluster_health.axis.percent": "Health Percent",
"app.login.message-invalid-credentials":
"Invalid username or passwordadmin/888888",

View File

@ -76,6 +76,13 @@ export default {
"cluster.metrics.node.axis.docs_count.title": "Document Count",
"cluster.metrics.node.axis.index_storage.title": "Indices Storage",
"cluster.metrics.node.axis.jvm_heap_used_percent.title": "JVM Heap Usage",
"cluster.metrics.node.axis.os_cpu.title": "OS CPU Percent",
"cluster.metrics.node.axis.os_used_mem.title": "OS Mem Usage",
"cluster.metrics.node.axis.indexing_pressure_memory.title":
"Indexing Pressure",
"cluster.metrics.node.axis.jvm_used_heap.title": "JVM Used Heap",
"cluster.metrics.node.axis.jvm_young_gc_rate.title": "Young GC Rate",
"cluster.metrics.node.axis.jvm_young_gc_latency.title": "Young GC Latency",
"cluster.metrics.index.axis.index_storage.title": "Index Storage",
"cluster.metrics.index.axis.doc_count.title": "Document count",
@ -105,4 +112,5 @@ export default {
"cluster.metrics.group.http": "Http Traffic",
"cluster.metrics.group.memory": "Memory",
"cluster.metrics.group.cache": "Cache",
"cluster.metrics.group.JVM": "JVM",
};

View File

@ -6,7 +6,8 @@ export default {
"navBar.lang": "语言",
"layout.user.appname": "极限数据管理后台",
"layout.user.appslogon": "极限科技的数据管理平台是东半球最好用的实时数据管理平台",
"layout.user.appslogon":
"极限科技的数据管理平台是东半球最好用的实时数据管理平台",
"app.setting.appname": "极限数据中心",
"layout.user.link.help": "帮助",
@ -209,8 +210,12 @@ export default {
"dashboard.charts.title.cluster_storage.axis.indices_storage": "索引存储",
"dashboard.charts.title.cluster_storage.axis.available_storage": "剩余存储",
"dashboard.charts.title.node_count.axis.count": "节点数",
"dashboard.charts.title.cluster_health.axis.percent": "健康状态百分比",
"dashboard.charts.title.cluster_documents.axis.documents": "文档总数",
"dashboard.charts.title.cluster_documents.axis.count": "文档总数",
"dashboard.charts.title.cluster_documents.axis.deleted": "文档删除数",
"dashboard.charts.title.cluster_indices.axis.count": "索引总数",
"dashboard.charts.title.cluster_documents.axis.counts": "分片总数",
"app.login.message-invalid-credentials": "账户或密码错误admin/888888",

View File

@ -77,6 +77,16 @@ export default {
"cluster.metrics.node.axis.docs_count.title": "Document Count",
"cluster.metrics.node.axis.index_storage.title": "Indices Storage",
"cluster.metrics.node.axis.jvm_heap_used_percent.title": "JVM Heap Usage",
"cluster.metrics.node.axis.os_cpu.title": "OS CPU Percent",
"cluster.metrics.node.axis.os_used_mem.title": "OS Mem Usage",
"cluster.metrics.node.axis.indexing_pressure_memory.title":
"Indexing Pressure",
"cluster.metrics.node.axis.jvm_used_heap.title": "JVM Used Heap",
"cluster.metrics.node.axis.jvm_young_gc_rate.title": "Young GC Rate",
"cluster.metrics.node.axis.jvm_young_gc_latency.title": "Young GC Latency",
"cluster.metrics.node.axis.jvm_mem_young_used.title": "Pools Young Used",
"cluster.metrics.node.axis.jvm_mem_young_peak_used.title":
"Pools Young Peak Used",
"cluster.metrics.index.axis.index_storage.title": "Index Storage",
"cluster.metrics.index.axis.doc_count.title": "Document count",
@ -106,4 +116,5 @@ export default {
"cluster.metrics.group.http": "Http Traffic",
"cluster.metrics.group.memory": "Memory",
"cluster.metrics.group.cache": "Cache",
"cluster.metrics.group.JVM": "JVM",
};

View File

@ -453,7 +453,7 @@ class ClusterMonitor extends PureComponent {
});
let clusterAvailable = true;
const { clusterStatus: cstatus, selectedCluster } = this.props;
if (cstatus && selectedCluster) {
if (cstatus && selectedCluster && cstatus[selectedCluster.id]) {
clusterAvailable = cstatus[selectedCluster.id].available;
}
@ -674,6 +674,7 @@ class ClusterMonitor extends PureComponent {
id={item.metric.label}
groupId={item.metric.group}
timeZone={timezone}
color={item.color}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}

View File

@ -28,6 +28,7 @@ const gorupOrder = [
"latency",
"storage",
"http",
"JVM",
"memory",
"cache",
];

View File

@ -10,6 +10,7 @@ import {
useEffect,
useMemo,
useRef,
useLayoutEffect,
} from "react";
import { useLocalStorage } from "@/lib/hooks/storage";
import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings";
@ -116,6 +117,9 @@ const consoleTabReducer = (state: any, action: any) => {
...state,
order: action.payload.order,
};
break;
case "init":
newState = action.payload;
default:
}
// setLocalState(newState);
@ -199,11 +203,16 @@ export const ConsoleUI = ({
});
if (panes.length == 0) {
removeLocalState();
//reset tabState
dispatch({
type: "init",
payload: initialDefaultState(),
});
//removeLocalState();
return;
}
setLocalState(tabState);
}, [tabState, clusterMap]);
}, [tabState, clusterMap, dispatch]);
const saveEditorContent = useCallback(
(content) => {

View File

@ -1,29 +1,43 @@
import React from 'react';
import {Card, Form, Icon, Input, InputNumber, Button, Switch, message, Spin} from 'antd';
import router from 'umi/router';
import React from "react";
import {
Card,
Form,
Icon,
Input,
InputNumber,
Button,
Switch,
message,
Spin,
} from "antd";
import router from "umi/router";
import styles from './Form.less';
import {connect} from "dva";
import NewCluster from './Step';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from "./Form.less";
import { connect } from "dva";
import NewCluster from "./Step";
import PageHeaderWrapper from "@/components/PageHeaderWrapper";
@Form.create()
@connect(({clusterConfig}) =>({
clusterConfig
@connect(({ clusterConfig }) => ({
clusterConfig,
}))
class ClusterForm extends React.Component{
class ClusterForm extends React.Component {
constructor(props) {
super(props);
let editValue = this.props.clusterConfig.editValue;
let needAuth = false;
if(editValue.basic_auth && typeof editValue.basic_auth.username !== 'undefined' && editValue.basic_auth.username !== ''){
if (
editValue.basic_auth &&
typeof editValue.basic_auth.username !== "undefined" &&
editValue.basic_auth.username !== ""
) {
needAuth = true;
}
this.state = {
confirmDirty: false,
needAuth: needAuth,
isLoading: false,
}
};
}
componentDidMount() {
//console.log(this.props.clusterConfig.editMode)
@ -31,8 +45,8 @@ class ClusterForm extends React.Component{
compareToFirstPassword = (rule, value, callback) => {
const { form } = this.props;
if (value && value !== form.getFieldValue('password')) {
callback('Two passwords that you enter is inconsistent!');
if (value && value !== form.getFieldValue("password")) {
callback("Two passwords that you enter is inconsistent!");
} else {
callback();
}
@ -41,16 +55,16 @@ class ClusterForm extends React.Component{
validateToNextPassword = (rule, value, callback) => {
const { form } = this.props;
if (value && this.state.confirmDirty) {
form.validateFields(['confirm'], { force: true });
form.validateFields(["confirm"], { force: true });
}
callback();
};
handleSubmit = () =>{
const {form, dispatch, clusterConfig} = this.props;
handleSubmit = () => {
const { form, dispatch, clusterConfig } = this.props;
form.validateFields((errors, values) => {
if(errors){
return
if (errors) {
return;
}
//console.log(values);
let newVals = {
@ -64,76 +78,78 @@ class ClusterForm extends React.Component{
enabled: values.enabled,
monitored: values.monitored,
version: values.version,
schema: values.isTLS === true ? 'https': 'http',
schema: values.isTLS === true ? "https" : "http",
// order: values.order,
}
if(clusterConfig.editMode === 'NEW') {
};
if (clusterConfig.editMode === "NEW") {
dispatch({
type: 'clusterConfig/addCluster',
type: "clusterConfig/addCluster",
payload: newVals,
}).then(function (rel){
if(rel){
message.success("添加成功")
router.push('/system/cluster');
}).then(function(rel) {
if (rel) {
message.success("添加成功");
router.push("/system/cluster");
}
});
}else{
} else {
newVals.id = clusterConfig.editValue.id;
dispatch({
type: 'clusterConfig/updateCluster',
type: "clusterConfig/updateCluster",
payload: newVals,
}).then(function (rel){
if(rel){
message.success("修改成功")
router.push('/system/cluster');
}).then(function(rel) {
if (rel) {
message.success("修改成功");
router.push("/system/cluster");
}
});
}
})
}
});
};
handleAuthChange = (val) => {
this.setState({
needAuth: val,
})
}
});
};
tryConnect = async ()=>{
const {dispatch, form} = this.props;
const values = await form.validateFields((errors, values) => {
if(errors){
tryConnect = async () => {
const { dispatch, form } = this.props;
const values = await form.validateFields((errors, values) => {
if (errors) {
return false;
}
let newVals = {
name: values.name,
host: values.host,
basic_auth: {
username: values.username,
password: values.password,
},
schema: values.isTLS === true ? 'https': 'http',
}
return values;
});
if(!values){
return
if (!values) {
return;
}
this.setState({isLoading: true})
let newVals = {
name: values.name,
host: values.host,
basic_auth: {
username: values.username,
password: values.password,
},
schema: values.isTLS === true ? "https" : "http",
};
this.setState({ isLoading: true });
const res = await dispatch({
type: 'clusterConfig/doTryConnect',
payload: values
type: "clusterConfig/doTryConnect",
payload: newVals,
});
if(res){
message.success('连接成功!')
if (res) {
message.success("连接成功!");
form.setFieldsValue({
version: res.version
})
version: res.version,
});
}
this.setState({isLoading: false})
}
this.setState({ isLoading: false });
};
render() {
const {getFieldDecorator} = this.props.form;
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
@ -156,95 +172,105 @@ class ClusterForm extends React.Component{
},
},
};
const {editValue, editMode} = this.props.clusterConfig;
const { editValue, editMode } = this.props.clusterConfig;
return (
<PageHeaderWrapper>
<Card title={editMode === 'NEW' ? '注册集群': '修改集群配置'}
extra={[<Button type="primary" onClick={()=>{
router.push('/system/cluster');
}}>返回</Button>]}
>
<Spin spinning={this.state.isLoading}>
<Form {...formItemLayout}>
<Form.Item label="集群名称">
{getFieldDecorator('name', {
initialValue: editValue.name,
rules: [
{
required: true,
message: 'Please input cluster name!',
},
],
})(<Input autoComplete='off' placeholder="cluster-name" />)}
</Form.Item>
<Form.Item label="集群地址">
{getFieldDecorator('host', {
initialValue: editValue.host,
rules: [
{
type: 'string',
pattern: /^[\w\.]+\:\d+$/, //(https?:\/\/)?
message: '请输入域名或 IP 地址和端口号',
},
{
required: true,
message: '请输入域名或 IP 地址和端口号!',
},
],
})(<Input placeholder="127.0.0.1:9200" />)}
</Form.Item>
<Form.Item style={{marginBottom:0}}>
{getFieldDecorator('version', {
initialValue: editValue.version,
rules: [
],
})(<Input type="hidden"/>)}
</Form.Item>
<Form.Item label="TLS">
{getFieldDecorator('isTLS', {
initialValue: editValue?.schema === "https",
})(
<Switch
defaultChecked={editValue?.schema === "https"}
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
<Form.Item label="是否需要身份验证">
<Switch
defaultChecked={this.state.needAuth}
onChange={this.handleAuthChange}
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>
</Form.Item>
{this.state.needAuth === true ? (<div>
<Form.Item label="用户名">
{getFieldDecorator('username', {
initialValue: editValue.basic_auth?.username,
rules: [
],
})(<Input autoComplete='off' />)}
</Form.Item>
<Form.Item label="密码" hasFeedback>
{getFieldDecorator('password', {
initialValue: editValue.basic_auth?.password,
rules: [
],
})(<Input.Password />)}
</Form.Item>
</div>):''}
{/* <Form.Item label="">
<PageHeaderWrapper>
<Card
title={editMode === "NEW" ? "注册集群" : "修改集群配置"}
extra={[
<Button
type="primary"
onClick={() => {
router.push("/system/cluster");
}}
>
返回
</Button>,
]}
>
<Spin spinning={this.state.isLoading}>
<Form {...formItemLayout}>
<Form.Item label="集群名称">
{getFieldDecorator("name", {
initialValue: editValue.name,
rules: [
{
required: true,
message: "Please input cluster name!",
},
],
})(<Input autoComplete="off" placeholder="cluster-name" />)}
</Form.Item>
<Form.Item label="集群地址">
{getFieldDecorator("host", {
initialValue: editValue.host,
rules: [
{
type: "string",
pattern: /^[\w\.]+\:\d+$/, //(https?:\/\/)?
message: "请输入域名或 IP 地址和端口号",
},
{
required: true,
message: "请输入域名或 IP 地址和端口号!",
},
],
})(<Input placeholder="127.0.0.1:9200" />)}
</Form.Item>
<Form.Item style={{ marginBottom: 0 }}>
{getFieldDecorator("version", {
initialValue: editValue.version,
rules: [],
})(<Input type="hidden" />)}
</Form.Item>
<Form.Item label="TLS">
{getFieldDecorator("isTLS", {
initialValue: editValue?.schema === "https",
})(
<Switch
defaultChecked={editValue?.schema === "https"}
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>
)}
</Form.Item>
<Form.Item label="是否需要身份验证">
<Switch
defaultChecked={this.state.needAuth}
onChange={this.handleAuthChange}
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>
</Form.Item>
{this.state.needAuth === true ? (
<div>
<Form.Item label="用户名">
{getFieldDecorator("username", {
initialValue: editValue.basic_auth?.username,
rules: [],
})(<Input autoComplete="off" />)}
</Form.Item>
<Form.Item label="密码" hasFeedback>
{getFieldDecorator("password", {
initialValue: editValue.basic_auth?.password,
rules: [],
})(<Input.Password />)}
</Form.Item>
</div>
) : (
""
)}
{/* <Form.Item label="">
{getFieldDecorator('order', {
initialValue: editValue.order || 0,
})(<InputNumber />)}
</Form.Item> */}
<Form.Item label="描述">
{getFieldDecorator('description', {
initialValue: editValue.description,
})(<Input.TextArea placeholder="集群应用描述" />)}
</Form.Item>
{/* <Form.Item label="">
<Form.Item label="描述">
{getFieldDecorator("description", {
initialValue: editValue.description,
})(<Input.TextArea placeholder="集群应用描述" />)}
</Form.Item>
{/* <Form.Item label="">
{getFieldDecorator('enabled', {
valuePropName: 'checked',
initialValue: typeof editValue.enabled === 'undefined' ? true: editValue.enabled,
@ -253,29 +279,34 @@ class ClusterForm extends React.Component{
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item> */}
<Form.Item label="启用监控">
{getFieldDecorator('monitored', {
valuePropName: 'checked',
initialValue: typeof editValue.monitored === 'undefined' ? true: editValue.monitored,
})(<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>)}
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button type="primary" onClick={this.handleSubmit}>
{editMode === 'NEW' ? '注册': '保存'}
</Button>
<Button style={{marginLeft: 15}} onClick={this.tryConnect}>
测试连接
</Button>
</Form.Item>
</Form>
</Spin>
</Card>
<Form.Item label="启用监控">
{getFieldDecorator("monitored", {
valuePropName: "checked",
initialValue:
typeof editValue.monitored === "undefined"
? true
: editValue.monitored,
})(
<Switch
checkedChildren={<Icon type="check" />}
unCheckedChildren={<Icon type="close" />}
/>
)}
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button type="primary" onClick={this.handleSubmit}>
{editMode === "NEW" ? "注册" : "保存"}
</Button>
<Button style={{ marginLeft: 15 }} onClick={this.tryConnect}>
测试连接
</Button>
</Form.Item>
</Form>
</Spin>
</Card>
</PageHeaderWrapper>
)
);
}
}
export default ClusterForm;
export default ClusterForm;

View File

@ -214,13 +214,17 @@ class Index extends React.Component {
});
};
componentDidMount() {
this.fetchData({});
const { pageSize } = this.props.clusterConfig;
this.fetchData({
size: pageSize,
});
}
handleSearchClick = () => {
const { form } = this.props;
this.fetchData({
name: form.getFieldValue("name"),
current: 1,
});
};
@ -268,6 +272,17 @@ class Index extends React.Component {
});
};
handleTableChange = (pagination, filters, sorter, extra) => {
const { form } = this.props;
const { pageSize, current } = pagination;
this.fetchData({
from: (current - 1) * pageSize,
size: pageSize,
name: form.getFieldValue("name"),
current,
});
};
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
@ -275,7 +290,7 @@ class Index extends React.Component {
wrapperCol: { span: 14 },
style: { marginBottom: 0 },
};
const { data } = this.props.clusterConfig;
const { total, data, pageSize, current } = this.props.clusterConfig;
return (
<PageHeaderWrapper
title={formatMessage({ id: "cluster.manage.title" })}
@ -342,7 +357,13 @@ class Index extends React.Component {
bordered
columns={this.columns}
dataSource={data}
onChange={this.handleTableChange}
rowKey="id"
pagination={{
pageSize: pageSize,
total: total?.value || total,
current: current,
}}
/>
</Card>
</PageHeaderWrapper>

View File

@ -13,6 +13,8 @@ export default {
state: {
editMode: "",
editValue: {},
pageSize: 20,
current: 1,
},
effects: {
*fetchClusterList({ payload }, { call, put, select }) {
@ -28,7 +30,10 @@ export default {
// }
yield put({
type: "saveData",
payload: res,
payload: {
...res,
current: payload.current,
},
});
},
*addCluster({ payload }, { call, put, select }) {

View File

@ -113,7 +113,9 @@ class Index extends PureComponent {
];
componentDidMount() {
this.fetchData();
this.fetchData({
current: 1,
});
}
fetchData = (params = {}) => {
@ -159,6 +161,7 @@ class Index extends PureComponent {
keyword: fieldsValue.keyword,
from: 0,
size: 10,
current: 1,
});
this.setState({
searchKey: fieldsValue.keyword,
@ -231,9 +234,19 @@ class Index extends PureComponent {
},
});
};
handleTableChange = (pagination, filters, sorter, extra) => {
const { form } = this.props;
const { pageSize, current } = pagination;
this.fetchData({
from: (current - 1) * pageSize,
size: pageSize,
keyword: form.getFieldValue("keyword"),
current,
});
};
render() {
const { data, total } = this.props.command;
const { data, total, pageSize, current } = this.props.command;
const {
modalVisible,
updateModalVisible,
@ -301,7 +314,12 @@ class Index extends PureComponent {
bordered
dataSource={data}
rowKey="id"
pagination={{ pageSize: 10 }}
pagination={{
pageSize: pageSize,
current: current,
total: total?.value || total,
}}
onChange={this.handleTableChange}
columns={this.columns}
/>
</div>

View File

@ -4,7 +4,10 @@ import { formatESSearchResult } from "@/lib/elasticsearch/util";
export default {
namespace: "command",
state: {},
state: {
pageSize: 20,
current: 1,
},
effects: {
*fetchCommandList({ payload }, { call, put, select }) {
let res = yield call(searchCommand, payload);
@ -15,7 +18,10 @@ export default {
res = formatESSearchResult(res);
yield put({
type: "saveData",
payload: res,
payload: {
...res,
current: payload.current,
},
});
},