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 package api
import ( import (
log "github.com/cihub/seelog"
"infini.sh/framework/core/api" "infini.sh/framework/core/api"
"infini.sh/framework/core/task"
"infini.sh/search-center/api/index_management" "infini.sh/search-center/api/index_management"
"infini.sh/search-center/config" "infini.sh/search-center/config"
"infini.sh/search-center/service/alerting" "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) api.HandleAPIMethod(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() {
err := index_management.SyncRebuildResult(cfg.Elasticsearch) // err := index_management.SyncRebuildResult(cfg.Elasticsearch)
if err != nil { // if err != nil {
log.Error(err) // log.Error(err)
} // }
}, // },
}) //})
} }

View File

@ -3,10 +3,14 @@ elasticsearch:
- name: default - name: default
enabled: true enabled: true
monitored: false 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: basic_auth:
username: elastic username: elastic
password: ZBdkVQUUdF1Sir4X4BGB password: infinilabs
#前端 UI HTTP 配置 #前端 UI HTTP 配置
web: web:

View File

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

View File

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

View File

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

View File

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

View File

@ -207,8 +207,12 @@ export default {
"Storage Usage", "Storage Usage",
"dashboard.charts.title.cluster_storage.axis.available_storage": "dashboard.charts.title.cluster_storage.axis.available_storage":
"Storage Available", "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.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": "app.login.message-invalid-credentials":
"Invalid username or passwordadmin/888888", "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.docs_count.title": "Document Count",
"cluster.metrics.node.axis.index_storage.title": "Indices Storage", "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.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.index_storage.title": "Index Storage",
"cluster.metrics.index.axis.doc_count.title": "Document count", "cluster.metrics.index.axis.doc_count.title": "Document count",
@ -105,4 +112,5 @@ export default {
"cluster.metrics.group.http": "Http Traffic", "cluster.metrics.group.http": "Http Traffic",
"cluster.metrics.group.memory": "Memory", "cluster.metrics.group.memory": "Memory",
"cluster.metrics.group.cache": "Cache", "cluster.metrics.group.cache": "Cache",
"cluster.metrics.group.JVM": "JVM",
}; };

View File

@ -6,7 +6,8 @@ export default {
"navBar.lang": "语言", "navBar.lang": "语言",
"layout.user.appname": "极限数据管理后台", "layout.user.appname": "极限数据管理后台",
"layout.user.appslogon": "极限科技的数据管理平台是东半球最好用的实时数据管理平台", "layout.user.appslogon":
"极限科技的数据管理平台是东半球最好用的实时数据管理平台",
"app.setting.appname": "极限数据中心", "app.setting.appname": "极限数据中心",
"layout.user.link.help": "帮助", "layout.user.link.help": "帮助",
@ -209,8 +210,12 @@ export default {
"dashboard.charts.title.cluster_storage.axis.indices_storage": "索引存储", "dashboard.charts.title.cluster_storage.axis.indices_storage": "索引存储",
"dashboard.charts.title.cluster_storage.axis.available_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": "分片总数", "dashboard.charts.title.cluster_documents.axis.counts": "分片总数",
"app.login.message-invalid-credentials": "账户或密码错误admin/888888", "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.docs_count.title": "Document Count",
"cluster.metrics.node.axis.index_storage.title": "Indices Storage", "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.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.index_storage.title": "Index Storage",
"cluster.metrics.index.axis.doc_count.title": "Document count", "cluster.metrics.index.axis.doc_count.title": "Document count",
@ -106,4 +116,5 @@ export default {
"cluster.metrics.group.http": "Http Traffic", "cluster.metrics.group.http": "Http Traffic",
"cluster.metrics.group.memory": "Memory", "cluster.metrics.group.memory": "Memory",
"cluster.metrics.group.cache": "Cache", "cluster.metrics.group.cache": "Cache",
"cluster.metrics.group.JVM": "JVM",
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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