feature: Add prometheus base component
add prometheus base component Change-Id: I4c3c646ddf94ae3c03e98601dbe301fa2ca9e933
This commit is contained in:
parent
5c3c89c78a
commit
e21ddcf1bd
@ -119,6 +119,13 @@ class SkylineClient extends Base {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'query',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'queryRange',
|
||||||
|
key: 'query_range',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
113
src/components/PrometheusChart/BaseCard.jsx
Normal file
113
src/components/PrometheusChart/BaseCard.jsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Card, Select } from 'antd';
|
||||||
|
import VisibleObserver from 'components/VisibleObserver';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
import FetchPrometheusStore from './store/FetchPrometheusStore';
|
||||||
|
import style from './style.less';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class BaseFetch extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
constructorParams: PropTypes.shape({
|
||||||
|
requestType: PropTypes.oneOf(['current', 'range']).isRequired,
|
||||||
|
metricKey: PropTypes.string.isRequired,
|
||||||
|
formatDataFn: PropTypes.func.isRequired,
|
||||||
|
typeKey: PropTypes.string,
|
||||||
|
deviceKey: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
params: PropTypes.object,
|
||||||
|
currentRange: PropTypes.array.isRequired,
|
||||||
|
interval: PropTypes.number.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
extra: PropTypes.func,
|
||||||
|
renderContent: PropTypes.func.isRequired,
|
||||||
|
visibleHeight: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
// src/pages/monitor/containers/PhysicalNode/index.less:91
|
||||||
|
// 为了不在视区范围内的时候,仍然撑开元素。防止出现在视区的时候挤开下面的元素。(由下往上翻)
|
||||||
|
visibleHeight: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const { constructorParams } = this.props;
|
||||||
|
|
||||||
|
this.store = new FetchPrometheusStore(constructorParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!isEqual(prevProps, this.props)) {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
const { params, currentRange, interval } = this.props;
|
||||||
|
this.store.fetchData({ params, currentRange, interval });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExtra() {
|
||||||
|
const { extra } = this.props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.store.device && this.store.devices.length !== 0 && (
|
||||||
|
<Select
|
||||||
|
defaultValue={this.store.device}
|
||||||
|
style={{ width: 150, marginRight: 16 }}
|
||||||
|
options={this.store.devices.map((i) => ({
|
||||||
|
label: i,
|
||||||
|
value: i,
|
||||||
|
}))}
|
||||||
|
onChange={(e) => this.store.handleDeviceChange.call(this.store, e)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{extra && extra(this.store)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isLoading } = this.store;
|
||||||
|
const { visibleHeight } = this.props;
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={style.remove_extra_padding}
|
||||||
|
bodyStyle={{
|
||||||
|
// padding 24
|
||||||
|
minHeight: visibleHeight + 48,
|
||||||
|
}}
|
||||||
|
title={this.props.title}
|
||||||
|
extra={this.renderExtra()}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<VisibleObserver style={{ width: '100%', height: visibleHeight }}>
|
||||||
|
{(visible) => (visible ? this.props.renderContent(this.store) : null)}
|
||||||
|
</VisibleObserver>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseFetch;
|
137
src/components/PrometheusChart/ChartCard.jsx
Normal file
137
src/components/PrometheusChart/ChartCard.jsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Chart, Line, Tooltip } from 'bizcharts';
|
||||||
|
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||||
|
import React from 'react';
|
||||||
|
import { ChartType, getXScale } from 'components/PrometheusChart/utils/utils';
|
||||||
|
import {
|
||||||
|
baseLineProps,
|
||||||
|
baseToolTipProps,
|
||||||
|
multilineProps,
|
||||||
|
} from 'components/PrometheusChart/utils/baseProps';
|
||||||
|
import { toJS } from 'mobx';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { merge } from 'lodash';
|
||||||
|
import ArrowsAltOutlined from '@ant-design/icons/lib/icons/ArrowsAltOutlined';
|
||||||
|
import { Button, Modal } from 'antd';
|
||||||
|
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||||
|
|
||||||
|
const ChartCard = (props) => {
|
||||||
|
const {
|
||||||
|
constructorParams,
|
||||||
|
params,
|
||||||
|
currentRange,
|
||||||
|
interval,
|
||||||
|
chartProps,
|
||||||
|
title,
|
||||||
|
extra,
|
||||||
|
isModal = false,
|
||||||
|
BaseContentConfig = {},
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
height,
|
||||||
|
scale,
|
||||||
|
chartType,
|
||||||
|
toolTipProps = baseToolTipProps,
|
||||||
|
} = chartProps;
|
||||||
|
|
||||||
|
let lineProps;
|
||||||
|
switch (chartType) {
|
||||||
|
case ChartType.ONELINE:
|
||||||
|
case ChartType.ONELINEDEVICES:
|
||||||
|
lineProps = baseLineProps;
|
||||||
|
break;
|
||||||
|
case ChartType.MULTILINE:
|
||||||
|
case ChartType.MULTILINEDEVICES:
|
||||||
|
lineProps = multilineProps;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lineProps = baseLineProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderContent = (store) => {
|
||||||
|
let data = toJS(store.data);
|
||||||
|
if (store.device) {
|
||||||
|
data = data.filter((d) => d.device === store.device);
|
||||||
|
}
|
||||||
|
scale.x = merge({}, getXScale(props.currentRange), scale.x || {});
|
||||||
|
return (
|
||||||
|
<Chart autoFit padding="auto" data={data} height={height} scale={scale}>
|
||||||
|
<Line {...lineProps} />
|
||||||
|
<Tooltip {...toolTipProps} />
|
||||||
|
</Chart>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getModalCardsParams = (store) => {
|
||||||
|
const pa = {};
|
||||||
|
if (params && Object.keys(params).includes('hostname')) {
|
||||||
|
pa.hostname = store.node.metric.hostname;
|
||||||
|
}
|
||||||
|
return pa;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalContent = observer(() => (
|
||||||
|
<div style={{ height: 520 }}>
|
||||||
|
<BaseContent
|
||||||
|
renderChartCards={(store) => (
|
||||||
|
<ChartCard
|
||||||
|
{...props}
|
||||||
|
currentRange={store.currentRange}
|
||||||
|
interval={store.interval}
|
||||||
|
params={getModalCardsParams(store)}
|
||||||
|
isModal
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{...BaseContentConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseCard
|
||||||
|
constructorParams={constructorParams}
|
||||||
|
params={params}
|
||||||
|
currentRange={currentRange}
|
||||||
|
interval={interval}
|
||||||
|
title={title}
|
||||||
|
extra={(s) => (
|
||||||
|
<>
|
||||||
|
{extra && extra(s)}
|
||||||
|
{!isModal && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ArrowsAltOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
Modal.info({
|
||||||
|
icon: null,
|
||||||
|
content: <ModalContent />,
|
||||||
|
width: 1200,
|
||||||
|
okText: t('OK'),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
renderContent={renderContent}
|
||||||
|
visibleHeight={height}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(ChartCard);
|
100
src/components/PrometheusChart/CircleWithRightLegend.jsx
Normal file
100
src/components/PrometheusChart/CircleWithRightLegend.jsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import {
|
||||||
|
Annotation,
|
||||||
|
Axis,
|
||||||
|
Chart,
|
||||||
|
Coordinate,
|
||||||
|
Interaction,
|
||||||
|
Interval,
|
||||||
|
Legend,
|
||||||
|
registerShape,
|
||||||
|
Tooltip,
|
||||||
|
} from 'bizcharts';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export default class CircleChart extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
data: PropTypes.array,
|
||||||
|
legendFontSize: PropTypes.number,
|
||||||
|
legendOffsetX: PropTypes.number,
|
||||||
|
middleFontSize: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
legendFontSize: 16,
|
||||||
|
legendOffsetX: -40,
|
||||||
|
middleFontSize: 30,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, legendFontSize, legendOffsetX, middleFontSize } = this.props;
|
||||||
|
const sliceNumber = 0.01; // 自定义 other 的图形,增加两条线
|
||||||
|
|
||||||
|
registerShape('interval', 'sliceShape', {
|
||||||
|
draw(cfg, container) {
|
||||||
|
const { points } = cfg;
|
||||||
|
let path = [];
|
||||||
|
path.push(['M', points[0].x, points[0].y]);
|
||||||
|
path.push(['L', points[1].x, points[1].y - sliceNumber]);
|
||||||
|
path.push(['L', points[2].x, points[2].y - sliceNumber]);
|
||||||
|
path.push(['L', points[3].x, points[3].y]);
|
||||||
|
path.push('Z');
|
||||||
|
// eslint-disable-next-line react/no-this-in-sfc
|
||||||
|
path = this.parsePath(path);
|
||||||
|
return container.addShape('path', {
|
||||||
|
attrs: {
|
||||||
|
fill: cfg.color,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Chart data={data} autoFit padding="auto" appendPadding={[0, 20, 0, 0]}>
|
||||||
|
<Coordinate type="theta" radius={0.8} innerRadius={0.75} />
|
||||||
|
<Axis visible={false} />
|
||||||
|
<Tooltip showTitle={false} />
|
||||||
|
<Interval
|
||||||
|
adjust="stack"
|
||||||
|
position="value"
|
||||||
|
color="type"
|
||||||
|
shape="sliceShape"
|
||||||
|
/>
|
||||||
|
<Annotation.Text
|
||||||
|
position={['50%', '50%']}
|
||||||
|
content={data.reduce((a, b) => a + b.value, 0)}
|
||||||
|
style={{
|
||||||
|
lineHeight: 240,
|
||||||
|
fontSize: middleFontSize,
|
||||||
|
fill: '#262626',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Legend
|
||||||
|
position="right"
|
||||||
|
offsetX={legendOffsetX}
|
||||||
|
itemName={{
|
||||||
|
style: {
|
||||||
|
fontSize: legendFontSize,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Interaction type="element-single-selected" />
|
||||||
|
</Chart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
74
src/components/PrometheusChart/component/BaseContent.jsx
Normal file
74
src/components/PrometheusChart/component/BaseContent.jsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import NodeSelect from 'components/PrometheusChart/component/NodeSelect';
|
||||||
|
import TimeRangeSelect from 'components/PrometheusChart/component/TimeRangeSelect';
|
||||||
|
import styles from './index.less';
|
||||||
|
import BaseMonitorStore from '../store/BaseMonitorStore';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class BaseContent extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
renderChartCards: PropTypes.func.isRequired,
|
||||||
|
renderTimeRangeSelect: PropTypes.bool,
|
||||||
|
renderNodeSelect: PropTypes.bool,
|
||||||
|
fetchNodesFunc: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
renderTimeRangeSelect: true,
|
||||||
|
renderNodeSelect: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.store = new BaseMonitorStore({
|
||||||
|
fetchNodesFunc: this.props.fetchNodesFunc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { renderNodeSelect } = this.props;
|
||||||
|
if (renderNodeSelect) {
|
||||||
|
this.store.getNodes();
|
||||||
|
} else {
|
||||||
|
this.store.setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { renderChartCards, renderTimeRangeSelect, renderNodeSelect } =
|
||||||
|
this.props;
|
||||||
|
return (
|
||||||
|
<div className={styles.header}>
|
||||||
|
{renderTimeRangeSelect && (
|
||||||
|
<TimeRangeSelect
|
||||||
|
style={{ marginBottom: 24 }}
|
||||||
|
store={this.store}
|
||||||
|
renderNodeSelect={renderNodeSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderNodeSelect && (
|
||||||
|
<NodeSelect style={{ marginBottom: 24 }} store={this.store} />
|
||||||
|
)}
|
||||||
|
{renderChartCards(this.store)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseContent;
|
54
src/components/PrometheusChart/component/NodeSelect.jsx
Normal file
54
src/components/PrometheusChart/component/NodeSelect.jsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Select, Spin } from 'antd';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class NodeSelect extends Component {
|
||||||
|
render() {
|
||||||
|
const { style } = this.props;
|
||||||
|
const { node, nodes, isLoading, handleNodeChange } = this.props.store;
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spin />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span style={{ color: 'black', fontSize: 14, fontWeight: 400 }}>
|
||||||
|
Node:{' '}
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={node.metric.instance}
|
||||||
|
onChange={handleNodeChange}
|
||||||
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
|
style={{ minWidth: 150 }}
|
||||||
|
>
|
||||||
|
{nodes.map((item) => (
|
||||||
|
<Option key={item.metric.instance} value={item.metric.instance}>
|
||||||
|
{item.metric.instance}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NodeSelect;
|
181
src/components/PrometheusChart/component/TimeRangeSelect.jsx
Normal file
181
src/components/PrometheusChart/component/TimeRangeSelect.jsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Button, ConfigProvider, DatePicker, Radio, Select } from 'antd';
|
||||||
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import i18n from 'core/i18n';
|
||||||
|
import enUS from 'antd/es/locale/en_US';
|
||||||
|
import zhCN from 'antd/es/locale/zh_CN';
|
||||||
|
import { getRange } from 'components/PrometheusChart/utils/utils';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class TimeRangeSelect extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
// const { store } = props;
|
||||||
|
this.state = {
|
||||||
|
filterValue: 0,
|
||||||
|
datePickerRange: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRefresh = () => {
|
||||||
|
const { filterValue } = this.state;
|
||||||
|
const { store, renderNodeSelect } = this.props;
|
||||||
|
if (filterValue > -1) {
|
||||||
|
const currentRange = getRange(filterValue);
|
||||||
|
store.handleRangePickerChange(currentRange, true);
|
||||||
|
} else {
|
||||||
|
const { datePickerRange } = this.state;
|
||||||
|
store.handleRangePickerChange(datePickerRange, true);
|
||||||
|
}
|
||||||
|
if (renderNodeSelect) {
|
||||||
|
store.getNodes();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleFilterChange = (e) => {
|
||||||
|
const { store } = this.props;
|
||||||
|
const currentRange = getRange(e.target.value);
|
||||||
|
this.setState({
|
||||||
|
filterValue: e.target.value,
|
||||||
|
datePickerRange: null,
|
||||||
|
});
|
||||||
|
store.handleRangePickerChange(currentRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRangePickerChange = (dates) => {
|
||||||
|
const { store } = this.props;
|
||||||
|
this.setState({
|
||||||
|
datePickerRange: dates || [moment().subtract(1, 'hours'), moment()],
|
||||||
|
filterValue: 4,
|
||||||
|
});
|
||||||
|
store.handleRangePickerChange(dates);
|
||||||
|
};
|
||||||
|
|
||||||
|
disableTime = (pickRange) => {
|
||||||
|
const now = moment();
|
||||||
|
if (now.isSame(pickRange, 'day')) {
|
||||||
|
if (now.isSame(pickRange, 'hour')) {
|
||||||
|
if (now.isSame(pickRange, 'minutes')) {
|
||||||
|
return {
|
||||||
|
disabledHours: () => filterRange(now.hour() + 1, 24),
|
||||||
|
disabledMinutes: () => filterRange(now.minute() + 1, 60),
|
||||||
|
disabledSeconds: () => filterRange(now.second() + 1, 60),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
disabledHours: () => filterRange(now.hour() + 1, 24),
|
||||||
|
disabledMinutes: () => filterRange(now.minute() + 1, 60),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
disabledHours: () => filterRange(now.hour() + 1, 24),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
disabledDate(current) {
|
||||||
|
// Can not select days after today
|
||||||
|
return current > moment().endOf('day');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { store } = this.props;
|
||||||
|
const { intervals, interval } = store;
|
||||||
|
const { filterValue, datePickerRange } = this.state;
|
||||||
|
const lang = i18n.getLocale();
|
||||||
|
const localeProvider = lang === 'en' ? enUS : zhCN;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={this.props.style}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
icon={<SyncOutlined />}
|
||||||
|
onClick={this.handleRefresh}
|
||||||
|
/>
|
||||||
|
<Radio.Group
|
||||||
|
value={filterValue}
|
||||||
|
onChange={this.handleFilterChange}
|
||||||
|
className={styles.range}
|
||||||
|
style={{
|
||||||
|
marginLeft: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Radio.Button value={0}>{t('Last Hour')}</Radio.Button>
|
||||||
|
<Radio.Button value={1}>{t('Last Day')}</Radio.Button>
|
||||||
|
<Radio.Button value={2}>{t('Last 7 Days')}</Radio.Button>
|
||||||
|
<Radio.Button value={3}>{t('Last 2 Weeks')}</Radio.Button>
|
||||||
|
<Radio.Button value={4} style={{ float: 'right', padding: 0 }}>
|
||||||
|
<ConfigProvider locale={localeProvider}>
|
||||||
|
<RangePicker
|
||||||
|
showTime={{
|
||||||
|
hideDisabledOptions: true,
|
||||||
|
defaultValue: [
|
||||||
|
moment('00:00:00', 'HH:mm:ss'),
|
||||||
|
moment('00:00:00', 'HH:mm:ss'),
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
disabledDate={this.disabledDate}
|
||||||
|
disabledTime={this.disableTime}
|
||||||
|
onChange={this.handleRangePickerChange}
|
||||||
|
value={datePickerRange}
|
||||||
|
bordered={false}
|
||||||
|
allowClear={false}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 20,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'rgba(0,0,0,.85)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Time Interval: ')}
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={interval}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
onChange={store.handleIntervalChange}
|
||||||
|
>
|
||||||
|
{intervals.map((d) => (
|
||||||
|
<Option key={d.value} value={d.value}>
|
||||||
|
{d.text}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterRange(start, end) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = start; i < end; i++) {
|
||||||
|
result.push(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimeRangeSelect;
|
58
src/components/PrometheusChart/component/index.less
Normal file
58
src/components/PrometheusChart/component/index.less
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
.header {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.range {
|
||||||
|
:global {
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
color: #0068ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.download {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-btn-icon-only {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.outer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topContent {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
517
src/components/PrometheusChart/metricDict.js
Normal file
517
src/components/PrometheusChart/metricDict.js
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
const metricDict = {
|
||||||
|
monitorOverview: {
|
||||||
|
alertInfo: {
|
||||||
|
url: [
|
||||||
|
'node_cpu_seconds_total',
|
||||||
|
'node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes',
|
||||||
|
],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
mode: 'idle',
|
||||||
|
node: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `1 - (avg by(instance) (irate(${url}[5m]))) > 0.8`,
|
||||||
|
(url) => `1 - (${url}) > 0.8`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
physicalCPUUsage: {
|
||||||
|
url: ['openstack_nova_vcpus_used', 'openstack_nova_vcpus_available'],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
physicalMemoryUsage: {
|
||||||
|
url: [
|
||||||
|
'openstack_nova_memory_used_bytes',
|
||||||
|
'openstack_nova_memory_available_bytes',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
physicalStorageUsage: {
|
||||||
|
url: ['ceph_cluster_total_used_bytes', 'ceph_cluster_total_bytes'],
|
||||||
|
},
|
||||||
|
computeNodeStatus: {
|
||||||
|
url: ['openstack_nova_agent_state'],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
service: 'nova-compute',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})by(services_state)`],
|
||||||
|
},
|
||||||
|
topHostCPUUsage: {
|
||||||
|
url: ['node_cpu_seconds_total'],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
mode: 'idle',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `topk(5, 100 - (avg(irate(${url}[30m])) by (instance) * 100))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
topHostDiskIOPS: {
|
||||||
|
url: [
|
||||||
|
'node_disk_reads_completed_total',
|
||||||
|
'node_disk_writes_completed_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `topk(5, avg(irate(${url}[10m])) by (instance))`,
|
||||||
|
(url) => `topk(5, avg(irate(${url}[10m])) by (instance))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
topHostMemoryUsage: {
|
||||||
|
url: ['node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes'],
|
||||||
|
finalFormatFunc: [(url) => `topk(5, (1 - ${url}) * 100)`],
|
||||||
|
},
|
||||||
|
topHostInterface: {
|
||||||
|
url: [
|
||||||
|
'node_network_receive_bytes_total',
|
||||||
|
'node_network_transmit_bytes_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `topk(5, avg(irate(${url}[5m])) by (instance))`,
|
||||||
|
(url) => `topk(5, avg(irate(${url}[5m])) by (instance))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cephHealthStatus: {
|
||||||
|
url: ['ceph_health_status'],
|
||||||
|
},
|
||||||
|
cephStorageUsage: {
|
||||||
|
url: ['ceph_cluster_total_used_bytes', 'ceph_cluster_total_bytes'],
|
||||||
|
},
|
||||||
|
cephStorageAllocate: {
|
||||||
|
url: [
|
||||||
|
'os_cinder_volume_pools_free_capacity_gb',
|
||||||
|
'os_cinder_volume_pools_total_capacity_gb',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
cephStorageClusterIOPS: {
|
||||||
|
url: ['ceph_osd_op_r', 'ceph_osd_op_w'],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
physicalNode: {
|
||||||
|
cpuCores: {
|
||||||
|
url: ['node_cpu_seconds_total'],
|
||||||
|
finalFormatFunc: [(url) => `count(${url}) by (cpu)`],
|
||||||
|
},
|
||||||
|
totalMem: {
|
||||||
|
url: ['node_memory_MemTotal_bytes'],
|
||||||
|
},
|
||||||
|
systemRunningTime: {
|
||||||
|
url: ['node_boot_time_seconds'],
|
||||||
|
},
|
||||||
|
fileSystemFreeSpace: {
|
||||||
|
url: ['node_filesystem_avail_bytes', 'node_filesystem_size_bytes'],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
fstype: ['ext4', 'xfs'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fstype: ['ext4', 'xfs'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cpuUsage: {
|
||||||
|
url: ['node_cpu_seconds_total'],
|
||||||
|
finalFormatFunc: [(url) => `avg by (mode)(irate(${url}[30m])) * 100`],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
mode: ['idle', 'system', 'user', 'iowait'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
memUsage: {
|
||||||
|
url: [
|
||||||
|
'node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes',
|
||||||
|
'node_memory_MemAvailable_bytes',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
diskIOPS: {
|
||||||
|
url: [
|
||||||
|
'node_disk_reads_completed_total',
|
||||||
|
'node_disk_writes_completed_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `irate(${url}[5m])`,
|
||||||
|
(url) => `irate(${url}[5m])`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
diskUsage: {
|
||||||
|
url: ['node_filesystem_free_bytes / node_filesystem_size_bytes'],
|
||||||
|
finalFormatFunc: [(url) => `(1 - ${url}) * 100`],
|
||||||
|
baseParams: [
|
||||||
|
{
|
||||||
|
device: ['/dev/.*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
systemLoad: {
|
||||||
|
url: ['node_load1', 'node_load5', 'node_load15'],
|
||||||
|
},
|
||||||
|
networkTraffic: {
|
||||||
|
url: [
|
||||||
|
'node_network_receive_bytes_total',
|
||||||
|
'node_network_transmit_bytes_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[10m]))`,
|
||||||
|
(url) => `sum(irate(${url}[10m]))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tcpConnections: {
|
||||||
|
url: ['node_netstat_Tcp_CurrEstab'],
|
||||||
|
},
|
||||||
|
networkErrors: {
|
||||||
|
url: [
|
||||||
|
'node_network_receive_errs_total',
|
||||||
|
'node_network_transmit_errs_total',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
networkDroppedPackets: {
|
||||||
|
url: [
|
||||||
|
'node_network_receive_drop_total',
|
||||||
|
'node_network_transmit_drop_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `irate(${url}[5m])`,
|
||||||
|
(url) => `irate(${url}[5m])`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storageCluster: {
|
||||||
|
cephHealthStatus: {
|
||||||
|
url: ['ceph_health_status'],
|
||||||
|
},
|
||||||
|
cephMonitorStatus: {
|
||||||
|
url: ['ceph_mon_quorum_status'],
|
||||||
|
},
|
||||||
|
cephPGS: {
|
||||||
|
url: ['ceph_pg_clean', 'ceph_pg_total-ceph_pg_clean'],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
storageClusterUsage: {
|
||||||
|
url: ['ceph_cluster_total_used_bytes', 'ceph_cluster_total_bytes'],
|
||||||
|
},
|
||||||
|
osdData: {
|
||||||
|
url: [
|
||||||
|
'ceph_osd_in == 1 and ceph_osd_up == 1',
|
||||||
|
'ceph_osd_in == 1 and ceph_osd_up == 0',
|
||||||
|
'ceph_osd_in == 0 and ceph_osd_up == 1',
|
||||||
|
'ceph_osd_in == 0 and ceph_osd_up == 0',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `count(${url})`,
|
||||||
|
(url) => `count(${url})`,
|
||||||
|
(url) => `count(${url})`,
|
||||||
|
(url) => `count(${url})`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avgPerOSD: {
|
||||||
|
url: ['ceph_osd_numpg'],
|
||||||
|
finalFormatFunc: [(url) => `avg(${url})`],
|
||||||
|
},
|
||||||
|
// avgOSDApplyLatency: {
|
||||||
|
// url: ['ceph_osd_apply_latency_ms'],
|
||||||
|
// finalFormatFunc: [
|
||||||
|
// url => `avg(${url})`,
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// avgOSDCommitLatency: {
|
||||||
|
// url: ['ceph_osd_commit_latency_ms'],
|
||||||
|
// finalFormatFunc: [
|
||||||
|
// url => `avg(${url})`,
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
poolCapacityUsage: {
|
||||||
|
url: [
|
||||||
|
'ceph_cluster_total_used_bytes',
|
||||||
|
'ceph_cluster_total_bytes-ceph_cluster_total_used_bytes',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
clusterOSDLatency: {
|
||||||
|
url: ['ceph_osd_apply_latency_ms', 'ceph_osd_commit_latency_ms'],
|
||||||
|
finalFormatFunc: [(url) => `avg(${url})`, (url) => `avg(${url})`],
|
||||||
|
},
|
||||||
|
clusterIOPS: {
|
||||||
|
url: ['ceph_osd_op_r', 'ceph_osd_op_w'],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
clusterBandwidth: {
|
||||||
|
url: ['ceph_osd_op_rw_in_bytes', 'ceph_osd_op_rw_out_bytes'],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
url: ['ceph_pool_metadata', 'ceph_osd_metadata'],
|
||||||
|
},
|
||||||
|
poolTab: {
|
||||||
|
url: [
|
||||||
|
'ceph_pg_total',
|
||||||
|
'ceph_pool_objects',
|
||||||
|
'ceph_pool_max_avail',
|
||||||
|
'(ceph_pool_stored/ceph_pool_max_avail)*100',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
osdTab: {
|
||||||
|
url: [
|
||||||
|
'ceph_osd_weight',
|
||||||
|
'ceph_osd_apply_latency_ms',
|
||||||
|
'ceph_osd_commit_latency_ms',
|
||||||
|
'(ceph_osd_stat_bytes_used/ceph_osd_stat_bytes)*100',
|
||||||
|
'ceph_osd_up',
|
||||||
|
'ceph_osd_stat_bytes',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openstackService: {
|
||||||
|
novaService: {
|
||||||
|
url: [
|
||||||
|
'openstack_nova_agent_state',
|
||||||
|
'openstack_nova_agent_state',
|
||||||
|
'node_process_total',
|
||||||
|
'node_process_total',
|
||||||
|
],
|
||||||
|
baseParams: [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
adminState: 'disabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'libvirtd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'libvirtd',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => url,
|
||||||
|
(url) => `sum_over_time(${url}[24h]) > 0`,
|
||||||
|
(url) => url,
|
||||||
|
(url) => `min_over_time(${url}[24h]) == 0`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
networkService: {
|
||||||
|
url: ['openstack_neutron_agent_state', 'openstack_neutron_agent_state'],
|
||||||
|
baseParams: [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
adminState: 'down',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => url,
|
||||||
|
(url) => `sum_over_time(${url}[24h]) > 0`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cinderService: {
|
||||||
|
url: ['openstack_cinder_agent_state', 'openstack_cinder_agent_state'],
|
||||||
|
baseParams: [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
service_state: 'down',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => url,
|
||||||
|
(url) => `sum_over_time(${url}[24h]) > 0`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
otherService: {
|
||||||
|
url: ['mysql_up', 'rabbitmq_identity_info', 'memcached_up'],
|
||||||
|
},
|
||||||
|
otherServiceMinOverTime: {
|
||||||
|
url: ['mysql_up', 'rabbitmq_identity_info', 'memcached_up'],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `min_over_time(${url}[24h]) == 0`,
|
||||||
|
(url) => `min_over_time(${url}[24h]) == 0`,
|
||||||
|
(url) => `min_over_time(${url}[24h]) == 0`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// heatMinOverTime: {
|
||||||
|
// url: ['os_heat_services_status', 'os_heat_services_status'],
|
||||||
|
// finalFormatFunc: [
|
||||||
|
// (url) => url,
|
||||||
|
// (url) => `min_over_time(${url}[24h]) == 0`,
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
mysqlService: {
|
||||||
|
runningTime: {
|
||||||
|
url: ['mysql_global_status_uptime'],
|
||||||
|
},
|
||||||
|
connectedThreads: {
|
||||||
|
url: ['mysql_global_status_threads_connected'],
|
||||||
|
},
|
||||||
|
runningThreads: {
|
||||||
|
url: ['mysql_global_status_threads_running'],
|
||||||
|
},
|
||||||
|
slowQuery: {
|
||||||
|
url: ['mysql_global_status_slow_queries'],
|
||||||
|
},
|
||||||
|
threadsActivityTrends_connected: {
|
||||||
|
url: ['mysql_global_status_threads_connected'],
|
||||||
|
},
|
||||||
|
mysqlActions: {
|
||||||
|
url: [
|
||||||
|
'mysql_global_status_commands_total',
|
||||||
|
'mysql_global_status_commands_total',
|
||||||
|
'mysql_global_status_commands_total',
|
||||||
|
],
|
||||||
|
baseParams: [
|
||||||
|
{ command: 'delete' },
|
||||||
|
{ command: 'insert' },
|
||||||
|
{ command: 'update' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
slowQueryChart: {
|
||||||
|
url: ['mysql_global_status_slow_queries'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
memcacheService: {
|
||||||
|
currentConnections: {
|
||||||
|
url: ['memcached_current_connections'],
|
||||||
|
},
|
||||||
|
totalConnections: {
|
||||||
|
url: ['memcached_connections_total'],
|
||||||
|
},
|
||||||
|
readWriteBytesTotal: {
|
||||||
|
url: ['memcached_read_bytes_total', 'memcached_written_bytes_total'],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `irate(${url}[20m])`,
|
||||||
|
(url) => `irate(${url}[20m])`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
evictions: {
|
||||||
|
url: ['memcached_slab_items_evicted_unfetched_total'],
|
||||||
|
},
|
||||||
|
itemsInCache: {
|
||||||
|
url: ['memcached_items_total'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rabbitMQService: {
|
||||||
|
serviceStatus: {
|
||||||
|
url: ['rabbitmq_identity_info'],
|
||||||
|
},
|
||||||
|
totalConnections: {
|
||||||
|
url: ['rabbitmq_connections_opened_total'],
|
||||||
|
},
|
||||||
|
totalQueues: {
|
||||||
|
url: ['rabbitmq_queues_created_total'],
|
||||||
|
},
|
||||||
|
totalExchanges: {
|
||||||
|
url: ['erlang_mnesia_tablewise_size'],
|
||||||
|
},
|
||||||
|
totalConsumers: {
|
||||||
|
url: ['rabbitmq_queue_consumers'],
|
||||||
|
},
|
||||||
|
publishedOut: {
|
||||||
|
url: ['rabbitmq_channel_messages_published_total'],
|
||||||
|
finalFormatFunc: [(url) => `sum(irate(${url}[20m]))`],
|
||||||
|
},
|
||||||
|
publishedIn: {
|
||||||
|
url: ['rabbitmq_channel_messages_confirmed_total'],
|
||||||
|
finalFormatFunc: [(url) => `sum(irate(${url}[20m]))`],
|
||||||
|
},
|
||||||
|
// totalMessage: {
|
||||||
|
// url: ['rabbitmq_overview_messages'],
|
||||||
|
// },
|
||||||
|
channel: {
|
||||||
|
url: ['rabbitmq_channels'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
haProxyService: {
|
||||||
|
backendStatus: {
|
||||||
|
url: ['haproxy_backend_up'],
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
url: [
|
||||||
|
'haproxy_frontend_current_sessions',
|
||||||
|
'haproxy_frontend_current_session_rate',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
httpResponse: {
|
||||||
|
url: [
|
||||||
|
'haproxy_frontend_http_responses_total',
|
||||||
|
'haproxy_backend_http_responses_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[5m])) by (code)`,
|
||||||
|
(url) => `sum(irate(${url}[5m])) by (code)`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
url: [
|
||||||
|
'haproxy_backend_current_sessions',
|
||||||
|
'haproxy_backend_current_session_rate',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [(url) => `sum(${url})`, (url) => `sum(${url})`],
|
||||||
|
},
|
||||||
|
bytes: {
|
||||||
|
url: [
|
||||||
|
'haproxy_frontend_bytes_in_total',
|
||||||
|
'haproxy_backend_bytes_in_total',
|
||||||
|
'haproxy_frontend_bytes_out_total',
|
||||||
|
'haproxy_backend_bytes_out_total',
|
||||||
|
],
|
||||||
|
finalFormatFunc: [
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
(url) => `sum(irate(${url}[5m]))`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instanceMonitor: {
|
||||||
|
cpu: {
|
||||||
|
url: ['virtual:kvm:cpu:usage'],
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
url: ['virtual:kvm:memory:used'],
|
||||||
|
},
|
||||||
|
network: {
|
||||||
|
url: [
|
||||||
|
'virtual:kvm:network:receive:rate',
|
||||||
|
'virtual:kvm:network:transmit:rate',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
disk: {
|
||||||
|
url: ['virtual:kvm:disk:read:kbps', 'virtual:kvm:disk:write:kbps'],
|
||||||
|
},
|
||||||
|
disk_iops: {
|
||||||
|
url: ['virtual:kvm:disk:read:iops', 'virtual:kvm:disk:write:iops'],
|
||||||
|
},
|
||||||
|
disk_usage: {
|
||||||
|
url: ['vm_disk_fs_used_pcent'],
|
||||||
|
finalFormatFunc: [(url) => `avg(${url}) without(hostname)`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metricDict;
|
116
src/components/PrometheusChart/store/BaseMonitorStore.js
Normal file
116
src/components/PrometheusChart/store/BaseMonitorStore.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { action, computed, observable } from 'mobx';
|
||||||
|
import {
|
||||||
|
defaultOneHourAgo,
|
||||||
|
fetchPrometheus,
|
||||||
|
getInterval,
|
||||||
|
} from 'components/PrometheusChart/utils/utils';
|
||||||
|
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { getTimestamp } from 'utils/time';
|
||||||
|
|
||||||
|
export default class BaseMonitorStore {
|
||||||
|
constructor(props) {
|
||||||
|
const { fetchNodesFunc } = props || {};
|
||||||
|
if (fetchNodesFunc) {
|
||||||
|
this.fetchNodesFunc = fetchNodesFunc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
nodes = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
node = {
|
||||||
|
metric: {
|
||||||
|
hostname: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable
|
||||||
|
currentRange = defaultOneHourAgo;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
interval = 10;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleRangePickerChange = async (dates, refresh = false) => {
|
||||||
|
if (
|
||||||
|
!refresh &&
|
||||||
|
!(
|
||||||
|
getTimestamp(this.currentRange[0]) === getTimestamp(dates[0]) &&
|
||||||
|
getTimestamp(this.currentRange[1]) === getTimestamp(dates[1])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// do not extract, see @compute get intervals functions
|
||||||
|
this.currentRange = dates;
|
||||||
|
this.interval = this.intervals[0].value;
|
||||||
|
} else {
|
||||||
|
// do not extract, see @compute get intervals functions
|
||||||
|
this.currentRange = dates;
|
||||||
|
}
|
||||||
|
// await this.getChartData();
|
||||||
|
};
|
||||||
|
|
||||||
|
@computed get intervals() {
|
||||||
|
return getInterval(this.currentRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleIntervalChange = async (interval) => {
|
||||||
|
this.interval = interval;
|
||||||
|
// await this.getChartData();
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getNodes = async () => {
|
||||||
|
this.isLoading = true;
|
||||||
|
let result = [];
|
||||||
|
try {
|
||||||
|
if (this.fetchNodesFunc) {
|
||||||
|
result = await this.fetchNodesFunc();
|
||||||
|
} else {
|
||||||
|
const query = 'node_load1';
|
||||||
|
const res = await fetchPrometheus(query, 'current', this.currentRange);
|
||||||
|
result = get(res, 'data.result', []);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.nodes = result;
|
||||||
|
this.node = this.nodes[0] || {
|
||||||
|
metric: {
|
||||||
|
hostname: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleNodeChange = async (instance) => {
|
||||||
|
const newNode = this.nodes.find(
|
||||||
|
(item) => item.metric.instance === instance
|
||||||
|
);
|
||||||
|
this.node = newNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
setLoading(flag) {
|
||||||
|
this.isLoading = flag;
|
||||||
|
}
|
||||||
|
}
|
126
src/components/PrometheusChart/store/FetchPrometheusStore.js
Normal file
126
src/components/PrometheusChart/store/FetchPrometheusStore.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { observable, action } from 'mobx';
|
||||||
|
import { get, isArray } from 'lodash';
|
||||||
|
import DataSet from '@antv/data-set';
|
||||||
|
import {
|
||||||
|
getRequestUrl,
|
||||||
|
fetchPrometheus,
|
||||||
|
} from 'components/PrometheusChart/utils/utils';
|
||||||
|
import metricDict from '../metricDict';
|
||||||
|
|
||||||
|
export default class FetchPrometheusStore {
|
||||||
|
type = '';
|
||||||
|
|
||||||
|
@observable
|
||||||
|
data = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
device;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
devices = [];
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
requestType,
|
||||||
|
metricKey,
|
||||||
|
formatDataFn,
|
||||||
|
typeKey,
|
||||||
|
deviceKey,
|
||||||
|
modifyKeys,
|
||||||
|
}) {
|
||||||
|
// 保存类型是range的还是current的
|
||||||
|
this.requestType = requestType;
|
||||||
|
// 获取初始串
|
||||||
|
this.queryParams = get(metricDict, metricKey);
|
||||||
|
// 格式化返回数据的方法
|
||||||
|
this.formatDataFn = formatDataFn || this.baseReturnFunc;
|
||||||
|
// 设置type的key
|
||||||
|
this.typeKey = typeKey;
|
||||||
|
// 设置device的key
|
||||||
|
this.deviceKey = deviceKey;
|
||||||
|
// 自定义type的值,用于tooltip的展示,否则一直会是y(方便处理)
|
||||||
|
this.modifyKeys = modifyKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReturnFunc = (d) => d;
|
||||||
|
|
||||||
|
@action
|
||||||
|
async fetchData({ params = {}, currentRange, interval }) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.device = undefined;
|
||||||
|
const promises = this.queryParams.url.map((u, idx) => {
|
||||||
|
// 按顺序取聚合函数
|
||||||
|
const finalFormatFunc =
|
||||||
|
(this.queryParams.finalFormatFunc || [])[idx] || this.baseReturnFunc;
|
||||||
|
// 按顺序获取基础参数
|
||||||
|
const baseParams = (this.queryParams.baseParams || [])[idx] || {};
|
||||||
|
const finalUrl = getRequestUrl(u, params, finalFormatFunc, baseParams);
|
||||||
|
return fetchPrometheus(
|
||||||
|
finalUrl,
|
||||||
|
this.requestType,
|
||||||
|
currentRange,
|
||||||
|
interval
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const res = await Promise.all(promises).catch((e) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(e);
|
||||||
|
return this.data;
|
||||||
|
});
|
||||||
|
this.formatData(res);
|
||||||
|
this.isLoading = false;
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatData(data) {
|
||||||
|
this.data = this.formatDataFn(
|
||||||
|
data,
|
||||||
|
this.typeKey,
|
||||||
|
this.deviceKey,
|
||||||
|
this.modifyKeys
|
||||||
|
);
|
||||||
|
if (isArray(this.data) && this.data.length !== 0 && this.data[0].device) {
|
||||||
|
const dv = new DataSet()
|
||||||
|
.createView()
|
||||||
|
.source(this.data)
|
||||||
|
.transform({
|
||||||
|
type: 'partition',
|
||||||
|
groupBy: ['device'],
|
||||||
|
});
|
||||||
|
this.devices = Object.keys(dv.rows).map((device) =>
|
||||||
|
device.slice(1, device.length)
|
||||||
|
);
|
||||||
|
this.device = this.devices[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleDeviceChange = (device) => {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.device = device;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateData = (data) => {
|
||||||
|
this.data = data;
|
||||||
|
};
|
||||||
|
}
|
21
src/components/PrometheusChart/style.less
Normal file
21
src/components/PrometheusChart/style.less
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.remove_extra_padding {
|
||||||
|
:global{
|
||||||
|
.ant-card-extra {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-head {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items:center;
|
||||||
|
|
||||||
|
.ant-card-loading-content {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/PrometheusChart/utils/baseProps.js
Normal file
27
src/components/PrometheusChart/utils/baseProps.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
export const baseLineProps = {
|
||||||
|
position: 'x*y',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const multilineProps = {
|
||||||
|
position: 'x*y',
|
||||||
|
color: 'type',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const baseToolTipProps = {
|
||||||
|
showCrosshairs: true,
|
||||||
|
shared: true,
|
||||||
|
};
|
61
src/components/PrometheusChart/utils/dataHandler.js
Normal file
61
src/components/PrometheusChart/utils/dataHandler.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
export function baseFixToChart(value) {
|
||||||
|
return {
|
||||||
|
x: value[0],
|
||||||
|
y: parseFloat(parseFloat(value[1]).toFixed(2)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export function handleResponses(
|
||||||
|
responses,
|
||||||
|
typeKey,
|
||||||
|
deviceKey,
|
||||||
|
modifyKeys = []
|
||||||
|
) {
|
||||||
|
const ret = [];
|
||||||
|
responses.forEach((response, idx) => {
|
||||||
|
ret.push(...handleResponse(response, typeKey, deviceKey, modifyKeys[idx]));
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleResponse(response, typeKey, deviceKey, modifyType) {
|
||||||
|
const { data } = response;
|
||||||
|
const ret = [];
|
||||||
|
data.result.forEach((result) => {
|
||||||
|
// values for range type & value for current type
|
||||||
|
const values = result.values || [result.value] || [];
|
||||||
|
values.forEach((value) => {
|
||||||
|
const item = {
|
||||||
|
...baseFixToChart(value),
|
||||||
|
};
|
||||||
|
if (typeKey) {
|
||||||
|
item.type = get(result.metric, typeKey);
|
||||||
|
}
|
||||||
|
if (deviceKey) {
|
||||||
|
item.device = get(result.metric, deviceKey);
|
||||||
|
}
|
||||||
|
if (modifyType) {
|
||||||
|
item.type = modifyType;
|
||||||
|
}
|
||||||
|
ret.push(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
217
src/components/PrometheusChart/utils/utils.js
Normal file
217
src/components/PrometheusChart/utils/utils.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { get, isArray } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { getStrFromTimestamp, getTimestamp } from 'utils/time';
|
||||||
|
import client from 'client';
|
||||||
|
import metricDict from '../metricDict';
|
||||||
|
|
||||||
|
// 给query串增加数据,如hostname等。
|
||||||
|
export function getRequestUrl(url, params, finalFormatFunc, baseParams) {
|
||||||
|
const totalParams = { ...params, ...baseParams };
|
||||||
|
return finalFormatFunc(
|
||||||
|
Object.keys(totalParams).length === 0 ? url : addParams(url, totalParams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addParams(query, params) {
|
||||||
|
let addStr = '';
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
if (isArray(params[key])) {
|
||||||
|
addStr += `${key}=~"${params[key].join('|')}",`;
|
||||||
|
} else {
|
||||||
|
addStr += `${key}="${params[key]}",`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `${query}{${addStr.substring(0, addStr.length - 1)}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPrometheus(
|
||||||
|
query,
|
||||||
|
getRangeType = 'range',
|
||||||
|
currentRange,
|
||||||
|
interval
|
||||||
|
) {
|
||||||
|
if (getRangeType === 'current') {
|
||||||
|
return client.skyline.query.list({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (getRangeType === 'range') {
|
||||||
|
return client.skyline.queryRange.list({
|
||||||
|
query,
|
||||||
|
start: getTimestamp(currentRange[0]),
|
||||||
|
end: getTimestamp(currentRange[1]),
|
||||||
|
step: interval,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBaseQuery(metricKey) {
|
||||||
|
let query = metricDict;
|
||||||
|
metricKey.split('.').forEach((key) => {
|
||||||
|
query = query[key];
|
||||||
|
});
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChartType = {
|
||||||
|
ONELINE: 'oneline',
|
||||||
|
MULTILINE: 'multiline',
|
||||||
|
ONELINEDEVICES: 'oneline_devices',
|
||||||
|
MULTILINEDEVICES: 'multiline_devices',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getXScale = (timeRange) => {
|
||||||
|
const rangeMinutes = moment(timeRange[1]).diff(
|
||||||
|
moment(timeRange[0]),
|
||||||
|
'minutes',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const index =
|
||||||
|
(rangeMinutes > 20160 && 4) ||
|
||||||
|
(rangeMinutes > 10080 && rangeMinutes <= 20160 && 3) ||
|
||||||
|
(rangeMinutes > 1440 && rangeMinutes <= 10080 && 2) ||
|
||||||
|
(rangeMinutes > 60 && rangeMinutes <= 1440 && 1) ||
|
||||||
|
(rangeMinutes > 0 && rangeMinutes <= 60 && 0) ||
|
||||||
|
0;
|
||||||
|
return {
|
||||||
|
type: 'time',
|
||||||
|
...maskAndTicketCountDict[index],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const baseReturnFunc = (d) => d;
|
||||||
|
|
||||||
|
export const getPromises = (metricKey) => {
|
||||||
|
const queries = get(metricDict, metricKey);
|
||||||
|
return queries.url.map((u, idx) => {
|
||||||
|
// 按顺序取聚合函数
|
||||||
|
const finalFormatFunc =
|
||||||
|
(queries.finalFormatFunc || [])[idx] || baseReturnFunc;
|
||||||
|
// 按顺序获取基础参数
|
||||||
|
const baseParams = (queries.baseParams || [])[idx] || {};
|
||||||
|
const finalUrl = getRequestUrl(u, {}, finalFormatFunc, baseParams);
|
||||||
|
return fetchPrometheus(finalUrl, 'current');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const maskAndTicketCountDict = [
|
||||||
|
{
|
||||||
|
// 一小时内的
|
||||||
|
// mask: 'HH:mm:ss',
|
||||||
|
formatter: (d) => getStrFromTimestamp(d, 'HH:mm:ss'),
|
||||||
|
ticketCount: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 一天内的
|
||||||
|
// mask: 'HH:mm:ss',
|
||||||
|
formatter: (d) => getStrFromTimestamp(d, 'HH:mm:ss'),
|
||||||
|
ticketCount: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 7天内的
|
||||||
|
// mask: 'MM-DD HH:mm',
|
||||||
|
formatter: (d) => getStrFromTimestamp(d, 'MM-DD HH:mm'),
|
||||||
|
ticketCount: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 14天内的
|
||||||
|
// mask: 'MM-DD HH:mm',
|
||||||
|
formatter: (d) => getStrFromTimestamp(d, 'MM-DD HH:mm'),
|
||||||
|
ticketCount: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 以上
|
||||||
|
// mask: 'MM-DD HH:mm',
|
||||||
|
formatter: (d) => getStrFromTimestamp(d, 'MM-DD HH:mm'),
|
||||||
|
ticketCount: 6,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const range2IntervalsDict = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('10s'),
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('1min'),
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('5min'),
|
||||||
|
value: 300,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('1min'),
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('5min'),
|
||||||
|
value: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('1H'),
|
||||||
|
value: 3600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('1H'),
|
||||||
|
value: 3600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('1D'),
|
||||||
|
value: 86400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('1D'),
|
||||||
|
value: 86400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getRange = (type) =>
|
||||||
|
({
|
||||||
|
// last 2 weeks
|
||||||
|
3: [moment().subtract(2, 'weeks'), moment()],
|
||||||
|
// last 7 days
|
||||||
|
2: [moment().subtract(1, 'weeks'), moment()],
|
||||||
|
// last day
|
||||||
|
1: [moment().subtract(1, 'days'), moment()],
|
||||||
|
// last hour
|
||||||
|
0: [moment().subtract(1, 'hours'), moment()],
|
||||||
|
}[type] || [moment().subtract(1, 'hours'), moment()]);
|
||||||
|
|
||||||
|
export function getInterval(currentRange) {
|
||||||
|
const start = (currentRange || getRange(0))[0];
|
||||||
|
const end = (currentRange || getRange(0))[1];
|
||||||
|
const rangeMinutes = end.diff(start, 'minutes');
|
||||||
|
const index =
|
||||||
|
(rangeMinutes > 44640 && 3) ||
|
||||||
|
(rangeMinutes > 1440 && rangeMinutes <= 44640 && 2) ||
|
||||||
|
(rangeMinutes > 60 && rangeMinutes <= 1440 && 1) ||
|
||||||
|
(rangeMinutes > 0 && rangeMinutes <= 60 && 0) ||
|
||||||
|
0;
|
||||||
|
return range2IntervalsDict[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 hour ago - now
|
||||||
|
export const defaultOneHourAgo = [moment().subtract(1, 'hours'), moment()];
|
62
src/components/VisibleObserver/index.jsx
Normal file
62
src/components/VisibleObserver/index.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Observer extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { visible: !window.IntersectionObserver };
|
||||||
|
this.io = null;
|
||||||
|
this.container = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
(window.IntersectionObserver
|
||||||
|
? Promise.resolve()
|
||||||
|
: import('intersection-observer')
|
||||||
|
).then(() => {
|
||||||
|
this.io = new window.IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
this.setState({ visible: entry.isIntersecting });
|
||||||
|
});
|
||||||
|
}, {});
|
||||||
|
this.io.observe(this.container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.io) {
|
||||||
|
this.io.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
// 这里也可以使用 findDOMNode 实现,但是不建议
|
||||||
|
<div
|
||||||
|
ref={(div) => {
|
||||||
|
this.container = div;
|
||||||
|
}}
|
||||||
|
{...this.props}
|
||||||
|
>
|
||||||
|
{Array.isArray(this.props.children)
|
||||||
|
? this.props.children.map((child) => child(this.state.visible))
|
||||||
|
: this.props.children(this.state.visible)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
393
src/resources/monitoring.js
Normal file
393
src/resources/monitoring.js
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import {
|
||||||
|
isEmpty,
|
||||||
|
isArray,
|
||||||
|
isNaN,
|
||||||
|
isUndefined,
|
||||||
|
isNumber,
|
||||||
|
isString,
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
last,
|
||||||
|
flatten,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} from 'lodash';
|
||||||
|
import { COLORS_MAP, MILLISECOND_IN_TIME_UNIT } from 'utils/constants';
|
||||||
|
import { getLocalTimeStr, getStrFromTimestamp } from 'utils/time';
|
||||||
|
|
||||||
|
const UnitTypes = {
|
||||||
|
second: {
|
||||||
|
conditions: [0.01, 0],
|
||||||
|
units: ['s', 'ms'],
|
||||||
|
},
|
||||||
|
cpu: {
|
||||||
|
conditions: [0.1, 0],
|
||||||
|
units: ['core', 'm'],
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
conditions: [1024 ** 4, 1024 ** 3, 1024 ** 2, 1024, 0],
|
||||||
|
units: ['TiB', 'GiB', 'MiB', 'KiB', 'Bytes'],
|
||||||
|
},
|
||||||
|
disk: {
|
||||||
|
conditions: [1000 ** 4, 1000 ** 3, 1000 ** 2, 1000, 0],
|
||||||
|
units: ['TB', 'GB', 'MB', 'KB', 'Bytes'],
|
||||||
|
},
|
||||||
|
throughput: {
|
||||||
|
conditions: [1000 ** 4, 1000 ** 3, 1000 ** 2, 1000, 0],
|
||||||
|
units: ['TB/s', 'GB/s', 'MB/s', 'KB/s', 'B/s'],
|
||||||
|
},
|
||||||
|
traffic: {
|
||||||
|
conditions: [1000 ** 4, 1000 ** 3, 1000 ** 2, 1000, 0],
|
||||||
|
units: ['TB/s', 'GB/s', 'MB/s', 'KB/s', 'B/s'],
|
||||||
|
},
|
||||||
|
bandwidth: {
|
||||||
|
conditions: [1024 ** 2 / 8, 1024 / 8, 0],
|
||||||
|
units: ['Mbps', 'Kbps', 'bps'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSuitableUnit = (value, unitType) => {
|
||||||
|
const config = UnitTypes[unitType];
|
||||||
|
|
||||||
|
if (isEmpty(config)) return '';
|
||||||
|
|
||||||
|
// value can be an array or a single value
|
||||||
|
const values = isArray(value) ? value : [[0, Number(value)]];
|
||||||
|
let result = last(config.units);
|
||||||
|
config.conditions.some((condition, index) => {
|
||||||
|
const triggered = values.some(
|
||||||
|
(_value) =>
|
||||||
|
((isArray(_value) ? get(_value, '[1]') : Number(_value)) || 0) >=
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
|
if (triggered) {
|
||||||
|
result = config.units[index];
|
||||||
|
}
|
||||||
|
return triggered;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSuitableValue = (
|
||||||
|
value,
|
||||||
|
unitType = 'default',
|
||||||
|
defaultValue = 0
|
||||||
|
) => {
|
||||||
|
if ((!isNumber(value) && !isString(value)) || isNaN(Number(value))) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const unit = getSuitableUnit(value, unitType);
|
||||||
|
const unitText = unit ? ` ${t(unit)}` : '';
|
||||||
|
const count = getValueByUnit(value, unit || unitType);
|
||||||
|
return `${count}${unitText}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValueByUnit = (num, unit) => {
|
||||||
|
let value = parseFloat(num);
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case '':
|
||||||
|
case 'default':
|
||||||
|
return value;
|
||||||
|
case 'iops':
|
||||||
|
return Math.round(value);
|
||||||
|
case '%':
|
||||||
|
value *= 100;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
value *= 1000;
|
||||||
|
if (value < 1) return 0;
|
||||||
|
break;
|
||||||
|
case 'KiB':
|
||||||
|
value /= 1024;
|
||||||
|
break;
|
||||||
|
case 'MiB':
|
||||||
|
value /= 1024 ** 2;
|
||||||
|
break;
|
||||||
|
case 'GiB':
|
||||||
|
value /= 1024 ** 3;
|
||||||
|
break;
|
||||||
|
case 'TiB':
|
||||||
|
value /= 1024 ** 4;
|
||||||
|
break;
|
||||||
|
case 'Bytes':
|
||||||
|
case 'B':
|
||||||
|
case 'B/s':
|
||||||
|
break;
|
||||||
|
case 'KB':
|
||||||
|
case 'KB/s':
|
||||||
|
value /= 1000;
|
||||||
|
break;
|
||||||
|
case 'MB':
|
||||||
|
case 'MB/s':
|
||||||
|
value /= 1000 ** 2;
|
||||||
|
break;
|
||||||
|
case 'GB':
|
||||||
|
case 'GB/s':
|
||||||
|
value /= 1000 ** 3;
|
||||||
|
break;
|
||||||
|
case 'TB':
|
||||||
|
case 'TB/s':
|
||||||
|
value /= 1000 ** 4;
|
||||||
|
break;
|
||||||
|
case 'bps':
|
||||||
|
value *= 8;
|
||||||
|
break;
|
||||||
|
case 'Kbps':
|
||||||
|
value = (value * 8) / 1024;
|
||||||
|
break;
|
||||||
|
case 'Mbps':
|
||||||
|
value = (value * 8) / 1024 / 1024;
|
||||||
|
break;
|
||||||
|
case 'ms':
|
||||||
|
value *= 1000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(value) === 0 ? 0 : Number(value.toFixed(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFormatTime = (ms) =>
|
||||||
|
getStrFromTimestamp(ms).replace(/:00$/g, '');
|
||||||
|
|
||||||
|
export const getChartData = ({
|
||||||
|
type,
|
||||||
|
unit,
|
||||||
|
xKey = 'time',
|
||||||
|
legend = [],
|
||||||
|
valuesData = [],
|
||||||
|
xFormatter,
|
||||||
|
}) => {
|
||||||
|
/*
|
||||||
|
build a value map => { 1566289260: {...} }
|
||||||
|
e.g. { 1566289260: { 'utilisation': 30.2 } }
|
||||||
|
*/
|
||||||
|
const valueMap = {};
|
||||||
|
valuesData.forEach((values, index) => {
|
||||||
|
values.forEach((item) => {
|
||||||
|
const time = parseInt(get(item, [0], 0), 10);
|
||||||
|
const value = get(item, [1]);
|
||||||
|
const key = get(legend, [index]);
|
||||||
|
|
||||||
|
if (time && !valueMap[time]) {
|
||||||
|
valueMap[time] = legend.reduce((obj, xAxisKey) => {
|
||||||
|
if (!obj[xAxisKey]) obj[xAxisKey] = null;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key && valueMap[time]) {
|
||||||
|
valueMap[time][key] =
|
||||||
|
value === '-1'
|
||||||
|
? null
|
||||||
|
: getValueByUnit(value, isUndefined(unit) ? type : unit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatter = (key) => (xKey === 'time' ? getFormatTime(key) : key);
|
||||||
|
|
||||||
|
// generate the chart data
|
||||||
|
const chartData = Object.entries(valueMap).map(([key, value]) => ({
|
||||||
|
[xKey]: (xFormatter || formatter)(key),
|
||||||
|
...value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return chartData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAreaChartOps = ({
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
unitType,
|
||||||
|
xKey = 'time',
|
||||||
|
legend = [],
|
||||||
|
data = [],
|
||||||
|
xFormatter,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const seriesData = isArray(data) ? data : [];
|
||||||
|
const valuesData = seriesData.map((result) => get(result, 'values') || []);
|
||||||
|
const unit = unitType
|
||||||
|
? getSuitableUnit(flatten(valuesData), unitType)
|
||||||
|
: rest.unit;
|
||||||
|
|
||||||
|
const chartData = getChartData({
|
||||||
|
type,
|
||||||
|
unit,
|
||||||
|
xKey,
|
||||||
|
legend,
|
||||||
|
valuesData,
|
||||||
|
xFormatter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const xAxisTickFormatter =
|
||||||
|
xKey === 'time' ? getXAxisTickFormatter(chartData) : (value) => value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
title,
|
||||||
|
unit,
|
||||||
|
xAxisTickFormatter,
|
||||||
|
data: chartData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getXAxisTickFormatter = (chartValus = []) => {
|
||||||
|
const timeList = chartValus.map(({ time }) => +new Date(time));
|
||||||
|
const minTime = min(timeList);
|
||||||
|
const maxTime = max(timeList);
|
||||||
|
|
||||||
|
if (maxTime - minTime > 8640000) {
|
||||||
|
return (time) => getLocalTimeStr(time, t('Do HH:mm'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (time) => getLocalTimeStr(time, 'HH:mm:ss');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLastMonitoringData = (data) => {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
|
const values = get(value, 'data.result[0].values', []) || [];
|
||||||
|
const _value = isEmpty(values)
|
||||||
|
? get(value, 'data.result[0].value', []) || []
|
||||||
|
: last(values);
|
||||||
|
set(result, `[${key}].value`, _value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTimesData = (data) => {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
data.forEach((record) => {
|
||||||
|
const values = get(record, 'values') || [];
|
||||||
|
|
||||||
|
values.forEach((value) => {
|
||||||
|
const time = get(value, '[0]', 0);
|
||||||
|
if (!result.includes(time)) {
|
||||||
|
result.push(time);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result.sort();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getZeroValues = () => {
|
||||||
|
const values = [];
|
||||||
|
let time = parseInt(Date.now() / 1000, 10) - 6000;
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
values[i] = [time, 0];
|
||||||
|
time += 600;
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getColorByName = (colorName = '#fff') =>
|
||||||
|
COLORS_MAP[colorName] || colorName;
|
||||||
|
|
||||||
|
export const startAutoRefresh = (context, options = {}) => {
|
||||||
|
const params = {
|
||||||
|
method: 'fetchData',
|
||||||
|
interval: 5000, // milliseconds
|
||||||
|
leading: true,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context && context[params.method]) {
|
||||||
|
const fetch = context[params.method];
|
||||||
|
|
||||||
|
if (params.leading) {
|
||||||
|
fetch({ autoRefresh: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
context.timer = setInterval(() => {
|
||||||
|
fetch({ autoRefresh: true });
|
||||||
|
}, params.interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopAutoRefresh = (context) => {
|
||||||
|
if (context && context.timer) {
|
||||||
|
clearInterval(context.timer);
|
||||||
|
context.timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSameDay = (preTime, nextTime) =>
|
||||||
|
Math.floor(preTime / 86400000) === Math.floor(nextTime / 86400000);
|
||||||
|
|
||||||
|
export const timeAliasReg = /(\d+)(\w+)/;
|
||||||
|
|
||||||
|
export const timestampify = (timeAlias) => {
|
||||||
|
const [, count = 0, unit] = timeAlias.match(timeAliasReg) || [];
|
||||||
|
return Number(count) * (MILLISECOND_IN_TIME_UNIT[unit] || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fillEmptyMetrics = (params, result) => {
|
||||||
|
if (!params.times || !params.start || !params.end) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const format = (num) => String(num).replace(/\..*$/, '');
|
||||||
|
const step = Math.floor((params.end - params.start) / params.times);
|
||||||
|
const correctCount = params.times + 1;
|
||||||
|
|
||||||
|
Object.values(result).forEach((item) => {
|
||||||
|
const _result = get(item, 'data.result');
|
||||||
|
if (!isEmpty(_result)) {
|
||||||
|
_result.forEach((resultItem) => {
|
||||||
|
const curValues = resultItem.values || [];
|
||||||
|
const curValuesMap = curValues.reduce(
|
||||||
|
(prev, cur) => ({
|
||||||
|
...prev,
|
||||||
|
[format(cur[0])]: cur[1],
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (curValues.length < correctCount) {
|
||||||
|
const newValues = [];
|
||||||
|
for (let index = 0; index < correctCount; index++) {
|
||||||
|
const time = format(params.start + index * step);
|
||||||
|
newValues.push([time, curValuesMap[time] || '0']);
|
||||||
|
}
|
||||||
|
resultItem.values = newValues;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cephStatusMap = {
|
||||||
|
0: t('Healthy'),
|
||||||
|
1: t('Warning'),
|
||||||
|
2: t('Error'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cephStatusColorMap = {
|
||||||
|
0: '#379738',
|
||||||
|
1: '#FAAD14',
|
||||||
|
2: '#D93126',
|
||||||
|
};
|
113
src/stores/prometheus/monitor-base.js
Normal file
113
src/stores/prometheus/monitor-base.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { action, computed, observable, set } from 'mobx';
|
||||||
|
import {
|
||||||
|
addParams,
|
||||||
|
defaultOneHourAgo,
|
||||||
|
getInterval,
|
||||||
|
} from 'components/PrometheusChart/utils/utils';
|
||||||
|
import { getTimestamp } from 'utils/time';
|
||||||
|
import Base from '../base';
|
||||||
|
|
||||||
|
export default class MonitorBase extends Base {
|
||||||
|
get responseKey() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
currentRange = defaultOneHourAgo;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
interval = 10;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleRangePickerChange = async (dates, refresh = false) => {
|
||||||
|
if (
|
||||||
|
!refresh &&
|
||||||
|
!(
|
||||||
|
getTimestamp(this.currentRange[0]) === getTimestamp(dates[0]) &&
|
||||||
|
getTimestamp(this.currentRange[1]) === getTimestamp(dates[1])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// do not extract, see @compute get intervals functions
|
||||||
|
this.currentRange = dates;
|
||||||
|
this.interval = this.intervals[0].value;
|
||||||
|
} else {
|
||||||
|
// do not extract, see @compute get intervals functions
|
||||||
|
this.currentRange = dates;
|
||||||
|
}
|
||||||
|
await this.getChartData();
|
||||||
|
};
|
||||||
|
|
||||||
|
@computed get intervals() {
|
||||||
|
return getInterval(this.currentRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRange = type => ({
|
||||||
|
// // last 2 weeks
|
||||||
|
// 3: [moment().subtract(2, 'weeks'), moment()],
|
||||||
|
// // last 7 days
|
||||||
|
// 2: [moment().subtract(1, 'weeks'), moment()],
|
||||||
|
// // last day
|
||||||
|
// 1: [moment().subtract(1, 'days'), moment()],
|
||||||
|
// // last hour
|
||||||
|
// 0: [moment().subtract(1, 'hours'), moment()],
|
||||||
|
// }[type] || [moment().subtract(1, 'hours'), moment()]);
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleIntervalChange = async (interval) => {
|
||||||
|
this.interval = interval;
|
||||||
|
await this.getChartData();
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleDeviceChange = (device, type) => {
|
||||||
|
const source = this[type];
|
||||||
|
set(source, {
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
const data = source.data.filter((item) => item.device === device);
|
||||||
|
setTimeout(() => {
|
||||||
|
set(source, {
|
||||||
|
currentDevice: device,
|
||||||
|
currentShowData: data,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
formatToGB(str) {
|
||||||
|
return parseFloat((parseInt(str, 10) / 1073741824).toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
buildRequest(query, getRangeType = 'range', params = {}) {
|
||||||
|
const newQueryStr =
|
||||||
|
Object.keys(params).length === 0 ? query : addParams(query, params);
|
||||||
|
if (getRangeType === 'current') {
|
||||||
|
return this.skylineClient.query.list({
|
||||||
|
query: newQueryStr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.skylineClient.queryRange.list({
|
||||||
|
query: newQueryStr,
|
||||||
|
start: getTimestamp(this.currentRange[0]),
|
||||||
|
end: getTimestamp(this.currentRange[1]),
|
||||||
|
step: this.interval,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
314
src/stores/prometheus/openstack-service.js
Normal file
314
src/stores/prometheus/openstack-service.js
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { action, observable, set } from 'mobx';
|
||||||
|
import { getPromises } from 'components/PrometheusChart/utils/utils';
|
||||||
|
import MonitorBase from './monitor-base';
|
||||||
|
|
||||||
|
const serviceNameMap = {
|
||||||
|
mysql_up: t('Database Service'),
|
||||||
|
rabbitmq_identity_info: t('Message Queue Service'),
|
||||||
|
memcached_up: t('Cache Service'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const indexToServiceName = [
|
||||||
|
t('Database Service'),
|
||||||
|
t('Message Queue Service'),
|
||||||
|
t('Cache Service'),
|
||||||
|
];
|
||||||
|
|
||||||
|
export class OpenstackServiceStore extends MonitorBase {
|
||||||
|
// @observable
|
||||||
|
// nodes = [];
|
||||||
|
|
||||||
|
// @observable
|
||||||
|
// node = {};
|
||||||
|
|
||||||
|
@observable
|
||||||
|
nova_service = {
|
||||||
|
isLoading: false,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable
|
||||||
|
network_service = {
|
||||||
|
isLoading: false,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable
|
||||||
|
cinder_service = {
|
||||||
|
isLoading: false,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable
|
||||||
|
other_service = {
|
||||||
|
isLoading: false,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getChartData = async () => {
|
||||||
|
// const { hostname } = this.node.metric;
|
||||||
|
const defaultPromises = [
|
||||||
|
this.getNovaService(),
|
||||||
|
this.getNetworkService(),
|
||||||
|
this.getCinderService(),
|
||||||
|
this.getOtherService(),
|
||||||
|
];
|
||||||
|
await Promise.all(defaultPromises);
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getNovaService = async () => {
|
||||||
|
set(this.nova_service, {
|
||||||
|
isLoading: true,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
const [currentState, last24State, libvirtdState, libvirtd24State] =
|
||||||
|
await Promise.all(getPromises.call(this, 'openstackService.novaService'));
|
||||||
|
const {
|
||||||
|
data: { result: currentStateResult },
|
||||||
|
} = currentState;
|
||||||
|
const tmp = [];
|
||||||
|
currentStateResult.forEach((service) => {
|
||||||
|
const {
|
||||||
|
metric: {
|
||||||
|
service: serviceName = '',
|
||||||
|
adminState = '',
|
||||||
|
hostname = '',
|
||||||
|
} = {},
|
||||||
|
} = service;
|
||||||
|
tmp.push({
|
||||||
|
hostname,
|
||||||
|
serviceName,
|
||||||
|
state: adminState === 'enabled' ? 'up' : 'down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { result: last24HResult },
|
||||||
|
} = last24State;
|
||||||
|
last24HResult.forEach((service) => {
|
||||||
|
const { metric: { service: serviceName = '', hostname = '' } = {} } =
|
||||||
|
service;
|
||||||
|
const idx = tmp.findIndex(
|
||||||
|
(item) => item.serviceName === serviceName && item.hostname === hostname
|
||||||
|
);
|
||||||
|
tmp[idx][`${serviceName}24`] = 'down';
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { result: data },
|
||||||
|
} = libvirtdState;
|
||||||
|
data.forEach((item) => {
|
||||||
|
const { metric, value } = item;
|
||||||
|
tmp.push({
|
||||||
|
// hard code
|
||||||
|
serviceName: 'nova_libvirt',
|
||||||
|
hostname: metric.hostname,
|
||||||
|
state: value[1] === 'enabled' ? 'up' : 'down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { result: libvirtd24Result },
|
||||||
|
} = libvirtd24State;
|
||||||
|
libvirtd24Result.forEach((service) => {
|
||||||
|
const { metric: { hostname = '' } = {} } = service;
|
||||||
|
const idx = tmp.findIndex(
|
||||||
|
(item) =>
|
||||||
|
item.serviceName === 'nova_libvirt' && item.hostname === hostname
|
||||||
|
);
|
||||||
|
tmp[idx].nova_libvirt24 = 'down';
|
||||||
|
});
|
||||||
|
set(this.nova_service, {
|
||||||
|
isLoading: false,
|
||||||
|
data: tmp,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getNetworkService = async () => {
|
||||||
|
set(this.network_service, {
|
||||||
|
isLoading: true,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
const [currentState, last24State] = await Promise.all(
|
||||||
|
getPromises.call(this, 'openstackService.networkService')
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
data: { result: currentStateResult },
|
||||||
|
} = currentState;
|
||||||
|
const tmp = [];
|
||||||
|
currentStateResult.forEach((service) => {
|
||||||
|
const {
|
||||||
|
metric: {
|
||||||
|
service: serviceName = '',
|
||||||
|
adminState = '',
|
||||||
|
hostname = '',
|
||||||
|
} = {},
|
||||||
|
} = service;
|
||||||
|
tmp.push({
|
||||||
|
serviceName,
|
||||||
|
hostname,
|
||||||
|
state: adminState,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { result: last24HResult },
|
||||||
|
} = last24State;
|
||||||
|
last24HResult.forEach((service) => {
|
||||||
|
const { metric: { service: serviceName = '', hostname = '' } = {} } =
|
||||||
|
service;
|
||||||
|
const idx = tmp.findIndex(
|
||||||
|
(item) => item.serviceName === serviceName && item.hostname === hostname
|
||||||
|
);
|
||||||
|
tmp[idx][`${serviceName}24`] = 'down';
|
||||||
|
});
|
||||||
|
set(this.network_service, {
|
||||||
|
isLoading: false,
|
||||||
|
data: tmp,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getCinderService = async () => {
|
||||||
|
set(this.cinder_service, {
|
||||||
|
isLoading: true,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
const [currentState, last24State] = await Promise.all(
|
||||||
|
getPromises.call(this, 'openstackService.cinderService')
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
data: { result: currentStateResult },
|
||||||
|
} = currentState;
|
||||||
|
const tmp = [];
|
||||||
|
currentStateResult.forEach((service) => {
|
||||||
|
const {
|
||||||
|
metric: {
|
||||||
|
service: serviceName = '',
|
||||||
|
adminState = '',
|
||||||
|
hostname = '',
|
||||||
|
} = {},
|
||||||
|
} = service;
|
||||||
|
tmp.push({
|
||||||
|
serviceName,
|
||||||
|
hostname,
|
||||||
|
state: adminState === 'enabled' ? 'up' : 'down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { result: last24HResult },
|
||||||
|
} = last24State;
|
||||||
|
last24HResult.forEach((service) => {
|
||||||
|
const { metric: { service: serviceName = '', hostname = '' } = {} } =
|
||||||
|
service;
|
||||||
|
const idx = tmp.findIndex(
|
||||||
|
(item) => item.serviceName === serviceName && item.hostname === hostname
|
||||||
|
);
|
||||||
|
tmp[idx][`${serviceName}24`] = 'down';
|
||||||
|
});
|
||||||
|
set(this.cinder_service, {
|
||||||
|
isLoading: false,
|
||||||
|
data: tmp,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getOtherService = async () => {
|
||||||
|
set(this.other_service, {
|
||||||
|
isLoading: true,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
const tmp = [];
|
||||||
|
let results = await Promise.all(
|
||||||
|
getPromises.call(this, 'openstackService.otherService')
|
||||||
|
);
|
||||||
|
results.forEach((result) => {
|
||||||
|
const {
|
||||||
|
data: { result: data },
|
||||||
|
} = result;
|
||||||
|
data.forEach((d) => {
|
||||||
|
const { metric, value } = d;
|
||||||
|
tmp.push({
|
||||||
|
serviceName: serviceNameMap[metric.__name__],
|
||||||
|
hostname: metric.instance,
|
||||||
|
state: value[1] === '1' ? 'up' : 'down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
results = await Promise.all(
|
||||||
|
getPromises.call(this, 'openstackService.otherServiceMinOverTime')
|
||||||
|
);
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
const {
|
||||||
|
data: { result: last24HResult },
|
||||||
|
} = result;
|
||||||
|
last24HResult.forEach((service) => {
|
||||||
|
const { metric: { instance = '' } = {} } = service;
|
||||||
|
const idx = tmp.findIndex(
|
||||||
|
(item) =>
|
||||||
|
item.serviceName === indexToServiceName[index] &&
|
||||||
|
item.hostname === instance
|
||||||
|
);
|
||||||
|
tmp[idx][`${indexToServiceName[index]}24`] = 'down';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// const [heatResponse, heat24Response] = await Promise.all(
|
||||||
|
// getPromises.call(this, 'openstackService.heatMinOverTime')
|
||||||
|
// );
|
||||||
|
// const {
|
||||||
|
// data: { result: heatResults },
|
||||||
|
// } = heatResponse;
|
||||||
|
// heatResults.forEach((item) => {
|
||||||
|
// const {
|
||||||
|
// metric: {
|
||||||
|
// host = '',
|
||||||
|
// binary = '',
|
||||||
|
// engine_id = '',
|
||||||
|
// services_status = '',
|
||||||
|
// } = {},
|
||||||
|
// } = item;
|
||||||
|
// tmp.push({
|
||||||
|
// serviceName: binary,
|
||||||
|
// host,
|
||||||
|
// state: services_status,
|
||||||
|
// engine_id,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// const {
|
||||||
|
// data: { result: heat24Results },
|
||||||
|
// } = heat24Response;
|
||||||
|
// heat24Results.forEach((result) => {
|
||||||
|
// const { metric: { binary = '', engine_id = '', host = '' } = {} } =
|
||||||
|
// result;
|
||||||
|
// const idx = tmp.findIndex(
|
||||||
|
// (item) =>
|
||||||
|
// item.serviceName === binary &&
|
||||||
|
// item.host === host &&
|
||||||
|
// item.engine_id === engine_id
|
||||||
|
// );
|
||||||
|
// tmp[idx][`${binary}24`] = 'down';
|
||||||
|
// });
|
||||||
|
|
||||||
|
set(this.other_service, {
|
||||||
|
isLoading: false,
|
||||||
|
data: tmp,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalOpenstackServiceStore = new OpenstackServiceStore();
|
||||||
|
|
||||||
|
export default globalOpenstackServiceStore;
|
61
src/stores/prometheus/storage-cluster.js
Normal file
61
src/stores/prometheus/storage-cluster.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2021 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { action, observable, set } from 'mobx';
|
||||||
|
import MonitorBase from 'stores/prometheus/monitor-base';
|
||||||
|
|
||||||
|
export class StorageClusterStore extends MonitorBase {
|
||||||
|
@observable
|
||||||
|
storageClusterUsage = {
|
||||||
|
isLoading: false,
|
||||||
|
data: {
|
||||||
|
used: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
getStorageClusterUsage = async (query = '') => {
|
||||||
|
set(this.storageClusterUsage, {
|
||||||
|
isLoading: true,
|
||||||
|
data: {
|
||||||
|
used: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const query1 = 'ceph_cluster_total_used_bytes';
|
||||||
|
const query2 = 'ceph_cluster_total_bytes';
|
||||||
|
const [used, total] = await Promise.all([
|
||||||
|
await this.buildRequest(query1 + query, 'current'),
|
||||||
|
await this.buildRequest(query2 + query, 'current'),
|
||||||
|
]);
|
||||||
|
set(this.storageClusterUsage, {
|
||||||
|
isLoading: false,
|
||||||
|
data: {
|
||||||
|
used:
|
||||||
|
used.data.result.length === 0
|
||||||
|
? 0
|
||||||
|
: this.formatToGB(used.data.result[0].value[1]),
|
||||||
|
total:
|
||||||
|
total.data.result.length === 0
|
||||||
|
? 0
|
||||||
|
: this.formatToGB(total.data.result[0].value[1]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalStorageClusterStore = new StorageClusterStore();
|
||||||
|
|
||||||
|
export default globalStorageClusterStore;
|
Loading…
Reference in New Issue
Block a user