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