modify cluster overview and alerting
This commit is contained in:
parent
879f529514
commit
947b0bfa04
|
@ -0,0 +1,87 @@
|
|||
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"
|
||||
"infini.sh/framework/modules/elastic/common"
|
||||
"net/http"
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
var (
|
||||
totalNode int
|
||||
totalStoreSize int
|
||||
hosts = map[string]struct{}{}
|
||||
)
|
||||
elastic.WalkConfigs(func(key, value interface{})bool{
|
||||
if handler.Config.Elasticsearch == key {
|
||||
return true
|
||||
}
|
||||
data, err := handler.getLatestClusterMonitorData(key)
|
||||
if err != nil{
|
||||
log.Error(err)
|
||||
}
|
||||
val, err := data.GetValue("cluster_stats.nodes.count.total")
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
if num, ok := val.(float64); ok {
|
||||
totalNode += int(num)
|
||||
}
|
||||
val, err = data.GetValue("index_stats._all.total.store.size_in_bytes")
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
if num, ok := val.(float64); ok {
|
||||
totalStoreSize += int(num)
|
||||
}
|
||||
|
||||
val, err = data.GetValue("agent.ip")
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
if ip, ok := val.(string); ok {
|
||||
hosts[ip] = struct{}{}
|
||||
}
|
||||
return true
|
||||
})
|
||||
resBody := util.MapStr{
|
||||
"total_node": totalNode,
|
||||
"total_store_size_in_bytes": totalStoreSize,
|
||||
"total_host": len(hosts),
|
||||
//"hosts": hosts,
|
||||
}
|
||||
handler.WriteJSON(w, resBody, http.StatusOK)
|
||||
}
|
||||
|
||||
func (handler APIHandler) getLatestClusterMonitorData(clusterID interface{}) (util.MapStr, error){
|
||||
client := elastic.GetClient(handler.Config.Elasticsearch)
|
||||
queryDSLTpl := `{
|
||||
"size": 1,
|
||||
"query": {
|
||||
"match": {
|
||||
"elasticsearch": "%s"
|
||||
}
|
||||
},
|
||||
"sort": [
|
||||
{
|
||||
"cluster_stats.timestamp": {
|
||||
"order": "desc"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
queryDSL := fmt.Sprintf(queryDSLTpl, clusterID)
|
||||
searchRes, err := client.SearchWithRawQueryDSL(orm.GetIndexName(common.MonitoringItem{}), []byte(queryDSL))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(searchRes.Hits.Hits) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return searchRes.Hits.Hits[0].Source, nil
|
||||
}
|
|
@ -17,6 +17,8 @@ func Init(cfg *config.AppConfig) {
|
|||
}
|
||||
var pathPrefix = "/_search-center/"
|
||||
//ui.HandleUIMethod(api.POST, "/api/get_indices",index_management.API1)
|
||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/overview"), handler.ElasticsearchOverviewAction)
|
||||
|
||||
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "dict/_search"), handler.GetDictListAction)
|
||||
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "dict/*id"), handler.CreateDictItemAction)
|
||||
//ui.HandleUIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction)
|
||||
|
|
|
@ -679,14 +679,14 @@ func ExecuteMonitor(w http.ResponseWriter, req *http.Request, ps httprouter.Para
|
|||
}
|
||||
monitorCtx, err := createMonitorContext(&trigger, resBody, &sm, IfaceMap{})
|
||||
if err != nil {
|
||||
triggerResult["error"] = err
|
||||
triggerResult["error"] = err.Error()
|
||||
triggerResults[trigger.ID] = triggerResult
|
||||
continue
|
||||
}
|
||||
isTrigger, err := resolveTriggerResult(&trigger, monitorCtx)
|
||||
triggerResult["triggered"] = isTrigger
|
||||
if err != nil {
|
||||
triggerResult["error"] = err
|
||||
triggerResult["error"] = err.Error()
|
||||
}
|
||||
if trigger.ID == "" {
|
||||
trigger.ID = util.GetUUID()
|
||||
|
|
|
@ -214,9 +214,9 @@ export default [
|
|||
},
|
||||
]
|
||||
},
|
||||
//
|
||||
//
|
||||
// //search
|
||||
|
||||
|
||||
//search
|
||||
// {
|
||||
// path: '/search',
|
||||
// name: 'search',
|
||||
|
|
|
@ -65,7 +65,7 @@ const SendRequestButton = (props: any) => {
|
|||
|
||||
interface ConsoleInputProps {
|
||||
clusterID: string,
|
||||
initialText: string,
|
||||
initialText: string | undefined,
|
||||
}
|
||||
|
||||
const DEFAULT_INPUT_VALUE = `GET _search
|
||||
|
@ -198,7 +198,7 @@ const ConsoleInputUI = ({clusterID, initialText}:ConsoleInputProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ConsoleInput = ({clusterID}:ConsoleInputProps)=>{
|
||||
const ConsoleInput = ({clusterID}:{clusterID:string})=>{
|
||||
const { done, error, retry } = useDataInit();
|
||||
const { currentTextObject } = useEditorReadContext();
|
||||
return done ? <ConsoleInputUI clusterID={clusterID} initialText={currentTextObject?.text}/>: <></>
|
||||
|
|
|
@ -35,7 +35,7 @@ export class Settings {
|
|||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
getFontSize() {
|
||||
return this.storage.get('font_size', 14);
|
||||
return this.storage.get('font_size', 12);
|
||||
}
|
||||
|
||||
setFontSize(size: any) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
const unitArr = Array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB");
|
||||
|
||||
export const formatter = {
|
||||
bytes: (value) => {
|
||||
if (isNaN(value) || null == value || value === ''||value==0) {
|
||||
return "0B";
|
||||
}
|
||||
var index = 0;
|
||||
var srcsize = parseFloat(value);
|
||||
index = Math.floor(Math.log(srcsize) / Math.log(1024));
|
||||
var size = srcsize / Math.pow(1024, index);
|
||||
size = size.toFixed(1);
|
||||
return {
|
||||
size,
|
||||
unit: unitArr[index],
|
||||
};
|
||||
},
|
||||
}
|
||||
|
|
@ -145,6 +145,7 @@ export default {
|
|||
'alert.trigger.edit.action.field.name': 'Action name',
|
||||
'alert.trigger.edit.action.field.destination': 'Destination',
|
||||
'alert.trigger.edit.action.field.message': 'Message',
|
||||
'alert.trigger.edit.action.field.message.info.paragraph1': 'You have access to a "_ctx" variable in your yaml scripts and action templates.',
|
||||
'alert.trigger.edit.action.field.message_subject': 'Message subject',
|
||||
'alert.trigger.edit.action.field.message_preview': 'Message preview',
|
||||
'alert.trigger.edit.action.send_test_message': 'Send test message',
|
||||
|
|
|
@ -143,7 +143,8 @@ export default {
|
|||
'alert.trigger.edit.action.button.add_action': '添加通知',
|
||||
'alert.trigger.edit.action.field.name': '通知名称',
|
||||
'alert.trigger.edit.action.field.destination': '渠道选择',
|
||||
'alert.trigger.edit.action.field.message': '消息配置',
|
||||
'alert.trigger.edit.action.field.message': '消息',
|
||||
'alert.trigger.edit.action.field.message.info.paragraph1': '您可以在 yaml 脚本中和通知模版中访问 "_ctx" 变量',
|
||||
'alert.trigger.edit.action.field.message_subject': '消息标题',
|
||||
'alert.trigger.edit.action.field.message_preview': '消息预览',
|
||||
'alert.trigger.edit.action.send_test_message': '发送测试消息',
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import React from 'react';
|
||||
import { EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { URL } from '../../../utils/constants';
|
||||
import {formatMessage} from 'umi/locale';
|
||||
|
||||
const message = () => ({
|
||||
flyoutProps: {
|
||||
|
@ -27,14 +28,14 @@ const message = () => ({
|
|||
header: (
|
||||
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
||||
<h2>
|
||||
<strong>Message</strong>
|
||||
<strong>{formatMessage({id:"alert.trigger.edit.action.field.message"})}</strong>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
),
|
||||
body: (
|
||||
<EuiText style={{ fontSize: '14px' }}>
|
||||
<p>
|
||||
{`You have access to a "_ctx" variable in your yaml scripts and action quicktemplate templates.`}
|
||||
{formatMessage({id:"alert.trigger.edit.action.field.message.info.paragraph1"})}
|
||||
</p>
|
||||
{/* <h3>Learn More</h3>
|
||||
<ul>
|
||||
|
|
|
@ -34,7 +34,7 @@ const CONTEXT_VARIABLES = JSON.stringify(
|
|||
const triggerCondition = context => ({
|
||||
flyoutProps: {
|
||||
'aria-labelledby': 'triggerConditionFlyout',
|
||||
maxWidth: 500,
|
||||
maxWidth: 540,
|
||||
size: 'm',
|
||||
},
|
||||
headerProps: { hasBorder: true },
|
||||
|
@ -50,6 +50,16 @@ const triggerCondition = context => ({
|
|||
<EuiText style={{ fontSize: '14px' }}>
|
||||
<p>{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph1'})}</p>
|
||||
<p>
|
||||
逻辑判断操作包括 or, and, not, in, range, equals, contains, regexp。其中 or, and, not 支持嵌套,如
|
||||
<EuiCodeBlock language="yaml">
|
||||
{`and:
|
||||
- range:
|
||||
_ctx.results.[0].hits.total.value.gt: 0
|
||||
- contains:
|
||||
_ctx.results.[0].hits.hits.[0]._source.field: value`}
|
||||
</EuiCodeBlock>
|
||||
</p>
|
||||
<p>
|
||||
{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph2'})}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
|
|
@ -5,6 +5,7 @@ import DestinationsList from './pages/Destinations/containers/DestinationsList';
|
|||
import {Fetch} from '../../components/kibana/core/public/http/fetch';
|
||||
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
||||
import {notification} from 'antd';
|
||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||
|
||||
const Destination = ({httpClient, notifications, history})=> {
|
||||
return (
|
||||
|
@ -85,6 +86,8 @@ export default (props)=>{
|
|||
}, [props.history])
|
||||
|
||||
return (
|
||||
<Destination httpClient={httpClient} notifications={notifications} history={history} />
|
||||
<PageHeaderWrapper>
|
||||
<Destination httpClient={httpClient} notifications={notifications} history={history} />
|
||||
</PageHeaderWrapper>
|
||||
)
|
||||
}
|
|
@ -6,6 +6,7 @@ import {useMemo} from 'react';
|
|||
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
||||
import {notification} from 'antd';
|
||||
import {connect} from 'dva'
|
||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||
|
||||
const httpClient = new Fetch({
|
||||
basePath:{
|
||||
|
@ -49,15 +50,17 @@ const AlertingUI = (props)=>{
|
|||
}, [props.history])
|
||||
|
||||
return (
|
||||
<CoreContext.Provider key={props.selectedCluster.id}
|
||||
value={{ http: httpClient, isDarkMode, notifications: notifications }}
|
||||
>
|
||||
<Router history={history}>
|
||||
<div style={{background:'#fff'}}>
|
||||
<Main title="Alerting" {...props} />
|
||||
</div>
|
||||
</Router>
|
||||
</CoreContext.Provider>
|
||||
<PageHeaderWrapper>
|
||||
<CoreContext.Provider key={props.selectedCluster.id}
|
||||
value={{ http: httpClient, isDarkMode, notifications: notifications }}
|
||||
>
|
||||
<Router history={history}>
|
||||
<div style={{background:'#fff'}}>
|
||||
<Main title="Alerting" {...props} />
|
||||
</div>
|
||||
</Router>
|
||||
</CoreContext.Provider>
|
||||
</PageHeaderWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {useState, useEffect} from 'react';
|
|||
import './alertoverview.scss';
|
||||
import { formatMessage } from 'umi/locale';
|
||||
|
||||
|
||||
export const AlertOverview = (props: any)=>{
|
||||
const {httpClient, history} = props;
|
||||
const [data, setData] = useState({
|
||||
|
@ -16,8 +17,7 @@ export const AlertOverview = (props: any)=>{
|
|||
totalAlerts: 0,
|
||||
});
|
||||
|
||||
const getAlerts = _.debounce(
|
||||
(from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds, type) => {
|
||||
const getAlerts = (from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds, type) => {
|
||||
let params = {
|
||||
from,
|
||||
size,
|
||||
|
@ -31,41 +31,42 @@ export const AlertOverview = (props: any)=>{
|
|||
if(monitorIds){
|
||||
params["monitorIds"]= monitorIds;
|
||||
}
|
||||
httpClient.get('/alerting/alerts', { query: params }).then((resp:any) => {
|
||||
if (resp.ok) {
|
||||
return httpClient.get('/alerting/alerts', { query: params })
|
||||
}
|
||||
|
||||
const useData = (pageSize: number, page: number, type: string):[any,any] => {
|
||||
const [size, setSize] = useState(pageSize || 10);
|
||||
const [pageIndex, setPageIndex] = useState(page || 1);
|
||||
const [alertData, setAlertData] = useState({
|
||||
data: [],
|
||||
total: 0,
|
||||
});
|
||||
useEffect(()=>{
|
||||
const from = (pageIndex - 1) * size;
|
||||
const fetchAlerts = async (from: number, size: number)=>{
|
||||
const resp = await getAlerts(from, size,"", "start_time", "desc", "ALL", "ALL","", type);
|
||||
if(resp.ok){
|
||||
const { alerts, totalAlerts } = resp;
|
||||
if(type == 'ALERT_HISTORY'){
|
||||
setHistoryData({
|
||||
alerts,
|
||||
totalAlerts,
|
||||
});
|
||||
return;
|
||||
}
|
||||
setData({
|
||||
alerts,
|
||||
totalAlerts,
|
||||
});
|
||||
} else {
|
||||
console.log('error getting alerts:', resp);
|
||||
setAlertData({
|
||||
...alertData,
|
||||
data: alerts,
|
||||
total: totalAlerts,
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
500,
|
||||
{ leading: true }
|
||||
);
|
||||
}
|
||||
fetchAlerts(from,size);
|
||||
}, [pageIndex, size, type]);
|
||||
const changePage = (pageIndex: number) => {
|
||||
setPageIndex(pageIndex);
|
||||
}
|
||||
|
||||
return [alertData, changePage];
|
||||
}
|
||||
|
||||
const pageSize = 10;
|
||||
useEffect(()=>{
|
||||
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "ALERT");
|
||||
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "ALERT_HISTORY")
|
||||
},[])
|
||||
const [alerts, onAlertPageChange] = useData(pageSize, 1, "ALERT");
|
||||
const [historyAlerts, onAlertHistoryPageChange] = useData(pageSize, 1, "ALERT_HISTORY");
|
||||
|
||||
const onPageChangeGen = (type:string) => {
|
||||
return (pageIndex: number)=>{
|
||||
const from = (pageIndex - 1) * pageSize;
|
||||
getAlerts(from, pageSize, "", "start_time", "desc", "ALL", "ALL","", type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const onItemClick = (item: any)=>{
|
||||
|
@ -75,21 +76,21 @@ export const AlertOverview = (props: any)=>{
|
|||
return (
|
||||
<div className="alert-overview">
|
||||
<div className="left">
|
||||
<AlertList dataSource={data.alerts}
|
||||
<AlertList dataSource={alerts.data as any}
|
||||
title={formatMessage({id:'alert.overview.alertlist.title'})}
|
||||
onItemClick={onItemClick}
|
||||
pagination={{
|
||||
pageSize,
|
||||
total: data.totalAlerts,
|
||||
onChange: onPageChangeGen('ALERT'),
|
||||
total: alerts.total,
|
||||
onChange: onAlertPageChange,
|
||||
}}/>
|
||||
<AlertList dataSource={historyData.alerts}
|
||||
<AlertList dataSource={historyAlerts.data}
|
||||
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
|
||||
onItemClick={onItemClick}
|
||||
pagination={{
|
||||
pageSize,
|
||||
total: historyData.totalAlerts,
|
||||
onChange: onPageChangeGen('ALERT_HISTORY'),
|
||||
total: historyAlerts.total,
|
||||
onChange: onAlertHistoryPageChange,
|
||||
}}/>
|
||||
</div>
|
||||
{/* <div className="right">
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import queryString from 'query-string';
|
||||
import { EuiBasicTable, EuiButton, EuiHorizontalRule, EuiIcon,RIGHT_ALIGNMENT,EuiButtonIcon } from '@elastic/eui';
|
||||
import { EuiBasicTable, EuiButton, EuiHorizontalRule, EuiIcon,RIGHT_ALIGNMENT,EuiButtonIcon,EuiDescriptionList } from '@elastic/eui';
|
||||
|
||||
import ContentPanel from '../../../components/ContentPanel';
|
||||
import DashboardEmptyPrompt from '../components/DashboardEmptyPrompt';
|
||||
|
@ -55,20 +55,6 @@ export default class Dashboard extends Component {
|
|||
sortField,
|
||||
} = this.getURLQueryParams();
|
||||
|
||||
const tableColumns = [...columns, {
|
||||
align: RIGHT_ALIGNMENT,
|
||||
width: '40px',
|
||||
isExpander: true,
|
||||
render: (item) => {
|
||||
const {itemIdToExpandedRowMap} = this.state;
|
||||
(
|
||||
<EuiButtonIcon
|
||||
onClick={() => toggleDetails(item)}
|
||||
aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'}
|
||||
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
|
||||
/>
|
||||
)},
|
||||
}]
|
||||
|
||||
this.state = {
|
||||
alerts: [],
|
||||
|
@ -83,7 +69,7 @@ export default class Dashboard extends Component {
|
|||
sortField,
|
||||
totalAlerts: 0,
|
||||
itemIdToExpandedRowMap:{},
|
||||
columns: tableColumns,
|
||||
columns: columns,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -305,6 +291,27 @@ export default class Dashboard extends Component {
|
|||
this.setState({ page });
|
||||
};
|
||||
|
||||
toggleDetails = (item)=>{
|
||||
const id = `${item.id}-${item.version}`;
|
||||
const itemIdToExpandedRowMapValues = { ...this.state.itemIdToExpandedRowMap };
|
||||
if (itemIdToExpandedRowMapValues[id]) {
|
||||
delete itemIdToExpandedRowMapValues[id];
|
||||
} else {
|
||||
const listItems = [
|
||||
{
|
||||
title: 'error',
|
||||
description: `${item.error_message}`,
|
||||
}
|
||||
]
|
||||
itemIdToExpandedRowMapValues[id] = (
|
||||
<EuiDescriptionList listItems={listItems} />
|
||||
);
|
||||
}
|
||||
this.setState({
|
||||
itemIdToExpandedRowMap:itemIdToExpandedRowMapValues,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
alerts,
|
||||
|
@ -316,9 +323,28 @@ export default class Dashboard extends Component {
|
|||
sortDirection,
|
||||
sortField,
|
||||
totalAlerts,
|
||||
columns,
|
||||
itemIdToExpandedRowMap,
|
||||
} = this.state;
|
||||
const { monitorIds, detectorIds, onCreateTrigger } = this.props;
|
||||
|
||||
const tableColumns = [...columns, {
|
||||
align: RIGHT_ALIGNMENT,
|
||||
width: '40px',
|
||||
isExpander: true,
|
||||
render: (item) => {
|
||||
if(item.state != 'ERROR'){
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
onClick={() => this.toggleDetails(item)}
|
||||
aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'}
|
||||
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
|
||||
/>
|
||||
)},
|
||||
}]
|
||||
|
||||
const pagination = {
|
||||
pageIndex: page,
|
||||
pageSize: size,
|
||||
|
@ -376,6 +402,7 @@ export default class Dashboard extends Component {
|
|||
<EuiBasicTable
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
isExpandable={true}
|
||||
hasActions={true}
|
||||
items={alerts}
|
||||
/*
|
||||
* If using just ID, doesn't update selectedItems when doing acknowledge
|
||||
|
@ -383,7 +410,7 @@ export default class Dashboard extends Component {
|
|||
* $id-$version will correctly remove selected items
|
||||
* */
|
||||
itemId={(item) => `${item.id}-${item.version}`}
|
||||
columns={this.state.columns}
|
||||
columns={tableColumns}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
isSelectable={true}
|
||||
|
|
|
@ -85,7 +85,7 @@ export const columns = [
|
|||
render: (state, alert) => {
|
||||
const stateText =
|
||||
typeof state !== 'string' ? DEFAULT_EMPTY_DATA : _.capitalize(state.toLowerCase());
|
||||
return state === ALERT_STATE.ERROR ? `${stateText}: ${alert.error_message}` : (stateOptions[state] || stateText);
|
||||
return (stateOptions[state] || stateText); // state === ALERT_STATE.ERROR ? `${stateText}: ${alert.error_message}` :
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -98,6 +98,17 @@ export default class Triggers extends Component {
|
|||
sortable: true,
|
||||
truncateText: false,
|
||||
},
|
||||
{
|
||||
actions: [{
|
||||
name: 'Edit',
|
||||
icon: 'documentEdit',
|
||||
type: 'icon',
|
||||
description: formatMessage({ id: 'form.button.edit' }),
|
||||
onClick: (item)=>{
|
||||
this.props.onEditTrigger(item);
|
||||
},
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
const sorting = { sort: { field, direction } };
|
||||
|
@ -110,9 +121,9 @@ export default class Triggers extends Component {
|
|||
titleSize="s"
|
||||
bodyStyles={{ padding: 'initial' }}
|
||||
actions={[
|
||||
<EuiButton onClick={this.onEdit} disabled={selectedItems.length !== 1}>
|
||||
{formatMessage({ id: 'form.button.edit' })}
|
||||
</EuiButton>,
|
||||
// <EuiButton onClick={this.onEdit} disabled={selectedItems.length !== 1}>
|
||||
// {formatMessage({ id: 'form.button.edit' })}
|
||||
// </EuiButton>,
|
||||
<EuiButton onClick={this.onDelete} disabled={!selectedItems.length}>
|
||||
{formatMessage({ id: 'form.button.delete' })}
|
||||
</EuiButton>,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react';
|
||||
import {List,Card,Row,Icon,Col} from "antd";
|
||||
import {List,Card,Row,Icon,Col, Table} from "antd";
|
||||
import styles from "./Overview.less";
|
||||
import {connect} from "dva";
|
||||
import {formatGigNumber} from "@/utils/utils";
|
||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||
import {formatter} from '@/lib/format';
|
||||
import Link from 'umi/link';
|
||||
|
||||
|
||||
const tabList = [
|
||||
|
@ -10,19 +13,21 @@ const tabList = [
|
|||
key: 'tabCluster',
|
||||
tab: '集群',
|
||||
},
|
||||
{
|
||||
key: 'tabHost',
|
||||
tab: '主机',
|
||||
},
|
||||
{
|
||||
key: 'tabNode',
|
||||
tab: '节点',
|
||||
},
|
||||
// {
|
||||
// key: 'tabHost',
|
||||
// tab: '主机',
|
||||
// },
|
||||
// {
|
||||
// key: 'tabNode',
|
||||
// tab: '节点',
|
||||
// },
|
||||
];
|
||||
|
||||
|
||||
@connect(({global}) => ({
|
||||
|
||||
@connect(({clusterOverview, global}) => ({
|
||||
clusterList: clusterOverview.clusterList,
|
||||
clusterStatus: global.clusterStatus,
|
||||
overview: clusterOverview.overview,
|
||||
}))
|
||||
|
||||
|
||||
|
@ -30,22 +35,134 @@ class Overview extends React.Component {
|
|||
state = {
|
||||
tabkey: 'tabCluster',
|
||||
};
|
||||
clusterColumns = [{
|
||||
title: '集群名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render:(val, item)=>{
|
||||
return <Link to={`/cluster/metrics/elasticsearch/${item.id}`}>{val}</Link>;
|
||||
}
|
||||
},{
|
||||
title: '健康状态',
|
||||
dataIndex: 'id',
|
||||
key: 'health_status',
|
||||
render: (val)=>{
|
||||
const {clusterStatus} = this.props;
|
||||
if(!clusterStatus || !clusterStatus[val]){
|
||||
return
|
||||
}
|
||||
const isAvailable = clusterStatus[val].available;
|
||||
if(!isAvailable){
|
||||
return <Icon type="close-circle" style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}}/>
|
||||
}
|
||||
const status = clusterStatus[val].health?.status;
|
||||
return <HealthStatusCircle status={status}/>
|
||||
|
||||
}
|
||||
},{
|
||||
title: '所属业务',
|
||||
dataIndex: 'business',
|
||||
key: 'business',
|
||||
render: ()=>{
|
||||
return 'eu-de-1'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'business_department',
|
||||
key: 'business_department',
|
||||
render: ()=>{
|
||||
return '部门X'
|
||||
}
|
||||
}, {
|
||||
title: '部署环境',
|
||||
dataIndex: 'deploy_env',
|
||||
key: 'deploy_env',
|
||||
render: ()=>{
|
||||
return 'PROD'
|
||||
}
|
||||
},{
|
||||
title: '程序版本',
|
||||
dataIndex: 'version',
|
||||
key: 'elasticsearch_version',
|
||||
// render: (data)=>{
|
||||
// return
|
||||
// }
|
||||
},{
|
||||
title: '节点数',
|
||||
dataIndex: 'id',
|
||||
key: 'number_of_nodes',
|
||||
render: (val)=>{
|
||||
const {clusterStatus} = this.props;
|
||||
if(!clusterStatus || !clusterStatus[val]){
|
||||
return
|
||||
}
|
||||
return clusterStatus[val].health?.number_of_nodes;
|
||||
}
|
||||
},{
|
||||
title: '集群地址',
|
||||
dataIndex: 'host',
|
||||
key: 'host',
|
||||
},
|
||||
{
|
||||
title: '监控启用状态',
|
||||
dataIndex: 'monitored',
|
||||
key: 'monitored',
|
||||
render: (val) => {
|
||||
return val? '启用': '关闭';
|
||||
}
|
||||
}]
|
||||
|
||||
onOperationTabChange = key => {
|
||||
this.setState({ tabkey: key });
|
||||
};
|
||||
|
||||
getClusterList = (params)=>{
|
||||
const {dispatch} = this.props;
|
||||
dispatch({
|
||||
type: 'clusterOverview/fetchClusterList',
|
||||
payload: params,
|
||||
})
|
||||
}
|
||||
getOverviewData = (params)=>{
|
||||
const {dispatch} = this.props;
|
||||
dispatch({
|
||||
type: 'clusterOverview/fetchOverview',
|
||||
})
|
||||
}
|
||||
componentDidMount() {
|
||||
this.getClusterList({size:20})
|
||||
this.getOverviewData();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { tabkey } = this.state;
|
||||
const {clusterList, overview} = this.props;
|
||||
|
||||
const contentList = {
|
||||
tabCluster: (
|
||||
<Card title="集群列表" style={{ marginBottom: 24 }} bordered={false}>
|
||||
<div>
|
||||
<Card style={{ marginBottom: 24 }} bordered={false}>
|
||||
{/* <div>
|
||||
<Icon type="frown-o" />
|
||||
暂无数据
|
||||
</div>
|
||||
</div> */}
|
||||
<Table
|
||||
bordered
|
||||
columns={this.clusterColumns}
|
||||
dataSource={clusterList?.data}
|
||||
rowKey='id'
|
||||
pagination={{
|
||||
total: clusterList?.total?.value,
|
||||
pageSize: 20,
|
||||
onChange: (page, pageSize)=>{
|
||||
this.getClusterList({
|
||||
size: pageSize,
|
||||
from: (page - 1)*pageSize,
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
),
|
||||
tabHost: (
|
||||
|
@ -65,6 +182,7 @@ class Overview extends React.Component {
|
|||
</Card>
|
||||
),
|
||||
};
|
||||
const totalStoreSize = formatter.bytes(overview?.total_store_size_in_bytes || 0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -76,7 +194,7 @@ class Overview extends React.Component {
|
|||
>
|
||||
<Card.Meta title='集群总数' className={styles.title} />
|
||||
<div>
|
||||
<span className={styles.total}>1</span>
|
||||
<span className={styles.total}>{clusterList?.total?.value}</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
@ -87,7 +205,7 @@ class Overview extends React.Component {
|
|||
>
|
||||
<Card.Meta title='主机总数' className={styles.title} />
|
||||
<div>
|
||||
<span className={styles.total}>1</span>
|
||||
<span className={styles.total}>{overview?.total_host}</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
@ -98,7 +216,7 @@ class Overview extends React.Component {
|
|||
>
|
||||
<Card.Meta title='节点总数' className={styles.title} />
|
||||
<div>
|
||||
<span className={styles.total}>1</span>
|
||||
<span className={styles.total}>{overview?.total_node}</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
@ -109,7 +227,7 @@ class Overview extends React.Component {
|
|||
>
|
||||
<Card.Meta title='存储空间' className={styles.title} />
|
||||
<div>
|
||||
<span className={styles.total}>100</span><span className={styles.unit}>GB</span>
|
||||
<span className={styles.total}>{totalStoreSize.size}</span><span className={styles.unit}>{totalStoreSize.unit}</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import {searchClusterConfig} from "@/services/cluster";
|
||||
import {getOverview} from "@/services/overview";
|
||||
import {message} from "antd";
|
||||
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
||||
|
||||
export default {
|
||||
namespace: 'clusterOverview',
|
||||
state: {
|
||||
|
||||
},
|
||||
effects:{
|
||||
*fetchClusterList({payload}, {call, put, select}){
|
||||
let res = yield call(searchClusterConfig, payload);
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
return false;
|
||||
}
|
||||
res = formatESSearchResult(res);
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
clusterList: res,
|
||||
}
|
||||
})
|
||||
},
|
||||
*fetchOverview({}, {call, put}){
|
||||
let res = yield call(getOverview);
|
||||
if(res.error){
|
||||
message.error(res.error)
|
||||
return false;
|
||||
}
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
overview: res,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
reducers:{
|
||||
saveData(state, {payload}){
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import request from '@/utils/request';
|
||||
import {pathPrefix} from './common';
|
||||
|
||||
export async function getOverview(params) {
|
||||
return request(`${pathPrefix}/elasticsearch/overview`, {
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue