modify cluster overview and alerting

This commit is contained in:
liugq 2021-10-08 14:53:47 +08:00
parent 879f529514
commit 947b0bfa04
20 changed files with 439 additions and 99 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -214,9 +214,9 @@ export default [
},
]
},
//
//
// //search
//search
// {
// path: '/search',
// name: 'search',

View File

@ -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}/>: <></>

View File

@ -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) {

19
web/src/lib/format.js Normal file
View File

@ -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],
};
},
}

View File

@ -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',

View File

@ -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': '发送测试消息',

View File

@ -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>

View File

@ -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>

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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">

View File

@ -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}

View File

@ -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}` :
},
},
{

View File

@ -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>,

View File

@ -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>

View File

@ -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
};
},
}
};

View File

@ -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'
});
}