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/"
|
var pathPrefix = "/_search-center/"
|
||||||
//ui.HandleUIMethod(api.POST, "/api/get_indices",index_management.API1)
|
//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.GET, path.Join(pathPrefix, "dict/_search"), handler.GetDictListAction)
|
||||||
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "dict/*id"), handler.CreateDictItemAction)
|
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "dict/*id"), handler.CreateDictItemAction)
|
||||||
//ui.HandleUIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction)
|
//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{})
|
monitorCtx, err := createMonitorContext(&trigger, resBody, &sm, IfaceMap{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
triggerResult["error"] = err
|
triggerResult["error"] = err.Error()
|
||||||
triggerResults[trigger.ID] = triggerResult
|
triggerResults[trigger.ID] = triggerResult
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isTrigger, err := resolveTriggerResult(&trigger, monitorCtx)
|
isTrigger, err := resolveTriggerResult(&trigger, monitorCtx)
|
||||||
triggerResult["triggered"] = isTrigger
|
triggerResult["triggered"] = isTrigger
|
||||||
if err != nil {
|
if err != nil {
|
||||||
triggerResult["error"] = err
|
triggerResult["error"] = err.Error()
|
||||||
}
|
}
|
||||||
if trigger.ID == "" {
|
if trigger.ID == "" {
|
||||||
trigger.ID = util.GetUUID()
|
trigger.ID = util.GetUUID()
|
||||||
|
|
|
@ -214,9 +214,9 @@ export default [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
//
|
|
||||||
//
|
|
||||||
// //search
|
//search
|
||||||
// {
|
// {
|
||||||
// path: '/search',
|
// path: '/search',
|
||||||
// name: 'search',
|
// name: 'search',
|
||||||
|
|
|
@ -65,7 +65,7 @@ const SendRequestButton = (props: any) => {
|
||||||
|
|
||||||
interface ConsoleInputProps {
|
interface ConsoleInputProps {
|
||||||
clusterID: string,
|
clusterID: string,
|
||||||
initialText: string,
|
initialText: string | undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_INPUT_VALUE = `GET _search
|
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 { done, error, retry } = useDataInit();
|
||||||
const { currentTextObject } = useEditorReadContext();
|
const { currentTextObject } = useEditorReadContext();
|
||||||
return done ? <ConsoleInputUI clusterID={clusterID} initialText={currentTextObject?.text}/>: <></>
|
return done ? <ConsoleInputUI clusterID={clusterID} initialText={currentTextObject?.text}/>: <></>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class Settings {
|
||||||
constructor(private readonly storage: Storage) {}
|
constructor(private readonly storage: Storage) {}
|
||||||
|
|
||||||
getFontSize() {
|
getFontSize() {
|
||||||
return this.storage.get('font_size', 14);
|
return this.storage.get('font_size', 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFontSize(size: any) {
|
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.name': 'Action name',
|
||||||
'alert.trigger.edit.action.field.destination': 'Destination',
|
'alert.trigger.edit.action.field.destination': 'Destination',
|
||||||
'alert.trigger.edit.action.field.message': 'Message',
|
'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_subject': 'Message subject',
|
||||||
'alert.trigger.edit.action.field.message_preview': 'Message preview',
|
'alert.trigger.edit.action.field.message_preview': 'Message preview',
|
||||||
'alert.trigger.edit.action.send_test_message': 'Send test message',
|
'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.button.add_action': '添加通知',
|
||||||
'alert.trigger.edit.action.field.name': '通知名称',
|
'alert.trigger.edit.action.field.name': '通知名称',
|
||||||
'alert.trigger.edit.action.field.destination': '渠道选择',
|
'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_subject': '消息标题',
|
||||||
'alert.trigger.edit.action.field.message_preview': '消息预览',
|
'alert.trigger.edit.action.field.message_preview': '消息预览',
|
||||||
'alert.trigger.edit.action.send_test_message': '发送测试消息',
|
'alert.trigger.edit.action.send_test_message': '发送测试消息',
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
import { EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
||||||
import { URL } from '../../../utils/constants';
|
import { URL } from '../../../utils/constants';
|
||||||
|
import {formatMessage} from 'umi/locale';
|
||||||
|
|
||||||
const message = () => ({
|
const message = () => ({
|
||||||
flyoutProps: {
|
flyoutProps: {
|
||||||
|
@ -27,14 +28,14 @@ const message = () => ({
|
||||||
header: (
|
header: (
|
||||||
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
||||||
<h2>
|
<h2>
|
||||||
<strong>Message</strong>
|
<strong>{formatMessage({id:"alert.trigger.edit.action.field.message"})}</strong>
|
||||||
</h2>
|
</h2>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
),
|
),
|
||||||
body: (
|
body: (
|
||||||
<EuiText style={{ fontSize: '14px' }}>
|
<EuiText style={{ fontSize: '14px' }}>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
{/* <h3>Learn More</h3>
|
{/* <h3>Learn More</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -34,7 +34,7 @@ const CONTEXT_VARIABLES = JSON.stringify(
|
||||||
const triggerCondition = context => ({
|
const triggerCondition = context => ({
|
||||||
flyoutProps: {
|
flyoutProps: {
|
||||||
'aria-labelledby': 'triggerConditionFlyout',
|
'aria-labelledby': 'triggerConditionFlyout',
|
||||||
maxWidth: 500,
|
maxWidth: 540,
|
||||||
size: 'm',
|
size: 'm',
|
||||||
},
|
},
|
||||||
headerProps: { hasBorder: true },
|
headerProps: { hasBorder: true },
|
||||||
|
@ -50,6 +50,16 @@ const triggerCondition = context => ({
|
||||||
<EuiText style={{ fontSize: '14px' }}>
|
<EuiText style={{ fontSize: '14px' }}>
|
||||||
<p>{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph1'})}</p>
|
<p>{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph1'})}</p>
|
||||||
<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'})}
|
{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph2'})}
|
||||||
</p>
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import DestinationsList from './pages/Destinations/containers/DestinationsList';
|
||||||
import {Fetch} from '../../components/kibana/core/public/http/fetch';
|
import {Fetch} from '../../components/kibana/core/public/http/fetch';
|
||||||
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
|
||||||
const Destination = ({httpClient, notifications, history})=> {
|
const Destination = ({httpClient, notifications, history})=> {
|
||||||
return (
|
return (
|
||||||
|
@ -85,6 +86,8 @@ export default (props)=>{
|
||||||
}, [props.history])
|
}, [props.history])
|
||||||
|
|
||||||
return (
|
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 {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
import {connect} from 'dva'
|
import {connect} from 'dva'
|
||||||
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
|
||||||
const httpClient = new Fetch({
|
const httpClient = new Fetch({
|
||||||
basePath:{
|
basePath:{
|
||||||
|
@ -49,15 +50,17 @@ const AlertingUI = (props)=>{
|
||||||
}, [props.history])
|
}, [props.history])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CoreContext.Provider key={props.selectedCluster.id}
|
<PageHeaderWrapper>
|
||||||
value={{ http: httpClient, isDarkMode, notifications: notifications }}
|
<CoreContext.Provider key={props.selectedCluster.id}
|
||||||
>
|
value={{ http: httpClient, isDarkMode, notifications: notifications }}
|
||||||
<Router history={history}>
|
>
|
||||||
<div style={{background:'#fff'}}>
|
<Router history={history}>
|
||||||
<Main title="Alerting" {...props} />
|
<div style={{background:'#fff'}}>
|
||||||
</div>
|
<Main title="Alerting" {...props} />
|
||||||
</Router>
|
</div>
|
||||||
</CoreContext.Provider>
|
</Router>
|
||||||
|
</CoreContext.Provider>
|
||||||
|
</PageHeaderWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {useState, useEffect} from 'react';
|
||||||
import './alertoverview.scss';
|
import './alertoverview.scss';
|
||||||
import { formatMessage } from 'umi/locale';
|
import { formatMessage } from 'umi/locale';
|
||||||
|
|
||||||
|
|
||||||
export const AlertOverview = (props: any)=>{
|
export const AlertOverview = (props: any)=>{
|
||||||
const {httpClient, history} = props;
|
const {httpClient, history} = props;
|
||||||
const [data, setData] = useState({
|
const [data, setData] = useState({
|
||||||
|
@ -16,8 +17,7 @@ export const AlertOverview = (props: any)=>{
|
||||||
totalAlerts: 0,
|
totalAlerts: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getAlerts = _.debounce(
|
const getAlerts = (from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds, type) => {
|
||||||
(from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds, type) => {
|
|
||||||
let params = {
|
let params = {
|
||||||
from,
|
from,
|
||||||
size,
|
size,
|
||||||
|
@ -31,41 +31,42 @@ export const AlertOverview = (props: any)=>{
|
||||||
if(monitorIds){
|
if(monitorIds){
|
||||||
params["monitorIds"]= monitorIds;
|
params["monitorIds"]= monitorIds;
|
||||||
}
|
}
|
||||||
httpClient.get('/alerting/alerts', { query: params }).then((resp:any) => {
|
return httpClient.get('/alerting/alerts', { query: params })
|
||||||
if (resp.ok) {
|
}
|
||||||
|
|
||||||
|
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;
|
const { alerts, totalAlerts } = resp;
|
||||||
if(type == 'ALERT_HISTORY'){
|
setAlertData({
|
||||||
setHistoryData({
|
...alertData,
|
||||||
alerts,
|
data: alerts,
|
||||||
totalAlerts,
|
total: totalAlerts,
|
||||||
});
|
})
|
||||||
return;
|
|
||||||
}
|
|
||||||
setData({
|
|
||||||
alerts,
|
|
||||||
totalAlerts,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('error getting alerts:', resp);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
},
|
fetchAlerts(from,size);
|
||||||
500,
|
}, [pageIndex, size, type]);
|
||||||
{ leading: true }
|
const changePage = (pageIndex: number) => {
|
||||||
);
|
setPageIndex(pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [alertData, changePage];
|
||||||
|
}
|
||||||
|
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
useEffect(()=>{
|
const [alerts, onAlertPageChange] = useData(pageSize, 1, "ALERT");
|
||||||
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "ALERT");
|
const [historyAlerts, onAlertHistoryPageChange] = useData(pageSize, 1, "ALERT_HISTORY");
|
||||||
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "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)=>{
|
const onItemClick = (item: any)=>{
|
||||||
|
@ -75,21 +76,21 @@ export const AlertOverview = (props: any)=>{
|
||||||
return (
|
return (
|
||||||
<div className="alert-overview">
|
<div className="alert-overview">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<AlertList dataSource={data.alerts}
|
<AlertList dataSource={alerts.data as any}
|
||||||
title={formatMessage({id:'alert.overview.alertlist.title'})}
|
title={formatMessage({id:'alert.overview.alertlist.title'})}
|
||||||
onItemClick={onItemClick}
|
onItemClick={onItemClick}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize,
|
pageSize,
|
||||||
total: data.totalAlerts,
|
total: alerts.total,
|
||||||
onChange: onPageChangeGen('ALERT'),
|
onChange: onAlertPageChange,
|
||||||
}}/>
|
}}/>
|
||||||
<AlertList dataSource={historyData.alerts}
|
<AlertList dataSource={historyAlerts.data}
|
||||||
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
|
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
|
||||||
onItemClick={onItemClick}
|
onItemClick={onItemClick}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize,
|
pageSize,
|
||||||
total: historyData.totalAlerts,
|
total: historyAlerts.total,
|
||||||
onChange: onPageChangeGen('ALERT_HISTORY'),
|
onChange: onAlertHistoryPageChange,
|
||||||
}}/>
|
}}/>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="right">
|
{/* <div className="right">
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import queryString from 'query-string';
|
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 ContentPanel from '../../../components/ContentPanel';
|
||||||
import DashboardEmptyPrompt from '../components/DashboardEmptyPrompt';
|
import DashboardEmptyPrompt from '../components/DashboardEmptyPrompt';
|
||||||
|
@ -55,20 +55,6 @@ export default class Dashboard extends Component {
|
||||||
sortField,
|
sortField,
|
||||||
} = this.getURLQueryParams();
|
} = 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 = {
|
this.state = {
|
||||||
alerts: [],
|
alerts: [],
|
||||||
|
@ -83,7 +69,7 @@ export default class Dashboard extends Component {
|
||||||
sortField,
|
sortField,
|
||||||
totalAlerts: 0,
|
totalAlerts: 0,
|
||||||
itemIdToExpandedRowMap:{},
|
itemIdToExpandedRowMap:{},
|
||||||
columns: tableColumns,
|
columns: columns,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +291,27 @@ export default class Dashboard extends Component {
|
||||||
this.setState({ page });
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
alerts,
|
alerts,
|
||||||
|
@ -316,9 +323,28 @@ export default class Dashboard extends Component {
|
||||||
sortDirection,
|
sortDirection,
|
||||||
sortField,
|
sortField,
|
||||||
totalAlerts,
|
totalAlerts,
|
||||||
|
columns,
|
||||||
|
itemIdToExpandedRowMap,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { monitorIds, detectorIds, onCreateTrigger } = this.props;
|
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 = {
|
const pagination = {
|
||||||
pageIndex: page,
|
pageIndex: page,
|
||||||
pageSize: size,
|
pageSize: size,
|
||||||
|
@ -376,6 +402,7 @@ export default class Dashboard extends Component {
|
||||||
<EuiBasicTable
|
<EuiBasicTable
|
||||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||||
isExpandable={true}
|
isExpandable={true}
|
||||||
|
hasActions={true}
|
||||||
items={alerts}
|
items={alerts}
|
||||||
/*
|
/*
|
||||||
* If using just ID, doesn't update selectedItems when doing acknowledge
|
* 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
|
* $id-$version will correctly remove selected items
|
||||||
* */
|
* */
|
||||||
itemId={(item) => `${item.id}-${item.version}`}
|
itemId={(item) => `${item.id}-${item.version}`}
|
||||||
columns={this.state.columns}
|
columns={tableColumns}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
isSelectable={true}
|
isSelectable={true}
|
||||||
|
|
|
@ -85,7 +85,7 @@ export const columns = [
|
||||||
render: (state, alert) => {
|
render: (state, alert) => {
|
||||||
const stateText =
|
const stateText =
|
||||||
typeof state !== 'string' ? DEFAULT_EMPTY_DATA : _.capitalize(state.toLowerCase());
|
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,
|
sortable: true,
|
||||||
truncateText: false,
|
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 } };
|
const sorting = { sort: { field, direction } };
|
||||||
|
@ -110,9 +121,9 @@ export default class Triggers extends Component {
|
||||||
titleSize="s"
|
titleSize="s"
|
||||||
bodyStyles={{ padding: 'initial' }}
|
bodyStyles={{ padding: 'initial' }}
|
||||||
actions={[
|
actions={[
|
||||||
<EuiButton onClick={this.onEdit} disabled={selectedItems.length !== 1}>
|
// <EuiButton onClick={this.onEdit} disabled={selectedItems.length !== 1}>
|
||||||
{formatMessage({ id: 'form.button.edit' })}
|
// {formatMessage({ id: 'form.button.edit' })}
|
||||||
</EuiButton>,
|
// </EuiButton>,
|
||||||
<EuiButton onClick={this.onDelete} disabled={!selectedItems.length}>
|
<EuiButton onClick={this.onDelete} disabled={!selectedItems.length}>
|
||||||
{formatMessage({ id: 'form.button.delete' })}
|
{formatMessage({ id: 'form.button.delete' })}
|
||||||
</EuiButton>,
|
</EuiButton>,
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
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 styles from "./Overview.less";
|
||||||
import {connect} from "dva";
|
import {connect} from "dva";
|
||||||
import {formatGigNumber} from "@/utils/utils";
|
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 = [
|
const tabList = [
|
||||||
|
@ -10,19 +13,21 @@ const tabList = [
|
||||||
key: 'tabCluster',
|
key: 'tabCluster',
|
||||||
tab: '集群',
|
tab: '集群',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: 'tabHost',
|
// key: 'tabHost',
|
||||||
tab: '主机',
|
// tab: '主机',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'tabNode',
|
// key: 'tabNode',
|
||||||
tab: '节点',
|
// 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 = {
|
state = {
|
||||||
tabkey: 'tabCluster',
|
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 => {
|
onOperationTabChange = key => {
|
||||||
this.setState({ tabkey: 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() {
|
render() {
|
||||||
|
|
||||||
const { tabkey } = this.state;
|
const { tabkey } = this.state;
|
||||||
|
const {clusterList, overview} = this.props;
|
||||||
|
|
||||||
const contentList = {
|
const contentList = {
|
||||||
tabCluster: (
|
tabCluster: (
|
||||||
<Card title="集群列表" style={{ marginBottom: 24 }} bordered={false}>
|
<Card style={{ marginBottom: 24 }} bordered={false}>
|
||||||
<div>
|
{/* <div>
|
||||||
<Icon type="frown-o" />
|
<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>
|
</Card>
|
||||||
),
|
),
|
||||||
tabHost: (
|
tabHost: (
|
||||||
|
@ -65,6 +182,7 @@ class Overview extends React.Component {
|
||||||
</Card>
|
</Card>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
const totalStoreSize = formatter.bytes(overview?.total_store_size_in_bytes || 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -76,7 +194,7 @@ class Overview extends React.Component {
|
||||||
>
|
>
|
||||||
<Card.Meta title='集群总数' className={styles.title} />
|
<Card.Meta title='集群总数' className={styles.title} />
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.total}>1</span>
|
<span className={styles.total}>{clusterList?.total?.value}</span>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -87,7 +205,7 @@ class Overview extends React.Component {
|
||||||
>
|
>
|
||||||
<Card.Meta title='主机总数' className={styles.title} />
|
<Card.Meta title='主机总数' className={styles.title} />
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.total}>1</span>
|
<span className={styles.total}>{overview?.total_host}</span>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -98,7 +216,7 @@ class Overview extends React.Component {
|
||||||
>
|
>
|
||||||
<Card.Meta title='节点总数' className={styles.title} />
|
<Card.Meta title='节点总数' className={styles.title} />
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.total}>1</span>
|
<span className={styles.total}>{overview?.total_node}</span>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -109,7 +227,7 @@ class Overview extends React.Component {
|
||||||
>
|
>
|
||||||
<Card.Meta title='存储空间' className={styles.title} />
|
<Card.Meta title='存储空间' className={styles.title} />
|
||||||
<div>
|
<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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</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