refact: Refact Monitor page
refact monitor page Change-Id: Ic7ff74641da63c236e27a146b6f1ab712a780f49
This commit is contained in:
parent
6872acf19b
commit
656ccfc9f6
@ -74,6 +74,7 @@
|
||||
"globals": {
|
||||
"t": true,
|
||||
"globals": true,
|
||||
"request": true
|
||||
"request": true,
|
||||
"METRICDICT": true
|
||||
}
|
||||
}
|
||||
|
@ -1,113 +1,149 @@
|
||||
// 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 React, { createContext, useEffect, useState } from 'react';
|
||||
import VisibleObserver from '../VisibleObserver';
|
||||
import { createFetchPrometheusClient, createDataHandler } from './utils';
|
||||
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,
|
||||
export const PrometheusContext = createContext({
|
||||
data: [],
|
||||
device: '',
|
||||
devices: [],
|
||||
});
|
||||
|
||||
function getChartData(data, device, devices) {
|
||||
if (device && devices.length !== 0) {
|
||||
return data.filter((d) => d.device === device);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const BaseCard = (props) => {
|
||||
const {
|
||||
createFetchParams,
|
||||
handleDataParams,
|
||||
fetchDataParams,
|
||||
title,
|
||||
visibleHeight,
|
||||
extra,
|
||||
renderContent,
|
||||
} = props;
|
||||
|
||||
const [initData, setInitData] = useState([]);
|
||||
|
||||
const [chartData, setChartData] = useState([]);
|
||||
|
||||
const [device, setDevice] = useState('');
|
||||
|
||||
const [devices, setDevices] = useState([]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const fetchData = createFetchPrometheusClient(createFetchParams);
|
||||
|
||||
const dataHandler = createDataHandler(handleDataParams);
|
||||
|
||||
const passedContext = {
|
||||
data: chartData,
|
||||
device,
|
||||
devices,
|
||||
modifyKeys: handleDataParams.modifyKeys,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
// src/pages/monitor/containers/PhysicalNode/index.less:91
|
||||
// 为了不在视区范围内的时候,仍然撑开元素。防止出现在视区的时候挤开下面的元素。(由下往上翻)
|
||||
visibleHeight: 100,
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
const data = await fetchData(fetchDataParams);
|
||||
const {
|
||||
retData: handledData,
|
||||
device: newDevice,
|
||||
devices: newDevices,
|
||||
} = dataHandler(data);
|
||||
// save base response
|
||||
setInitData(handledData);
|
||||
|
||||
// save device information
|
||||
setDevice(newDevice);
|
||||
setDevices(newDevices);
|
||||
|
||||
// refresh component
|
||||
const newChartData = getChartData(handledData, newDevice, newDevices);
|
||||
setChartData(newChartData);
|
||||
|
||||
// setLoading
|
||||
setIsLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const handleDeviceChange = (newDevice) => {
|
||||
setIsLoading(true);
|
||||
// refresh component
|
||||
const newChartData = getChartData(initData, newDevice, devices);
|
||||
setDevice(newDevice);
|
||||
setChartData(newChartData);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { constructorParams } = this.props;
|
||||
const filterChartData = (filt) => {
|
||||
setIsLoading(true);
|
||||
// refresh component
|
||||
const newChartData = initData.filter(filt);
|
||||
setChartData(newChartData);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
this.store = new FetchPrometheusStore(constructorParams);
|
||||
}
|
||||
const extraRender = (
|
||||
<>
|
||||
{!isLoading && device && devices.length !== 0 && (
|
||||
<Select
|
||||
defaultValue={device}
|
||||
style={{ width: 150, marginRight: 16 }}
|
||||
options={devices.map((i) => ({
|
||||
label: i,
|
||||
value: i,
|
||||
}))}
|
||||
onChange={handleDeviceChange}
|
||||
/>
|
||||
)}
|
||||
{extra &&
|
||||
extra({
|
||||
initData,
|
||||
chartData,
|
||||
device,
|
||||
devices,
|
||||
modifyKeys: handleDataParams.modifyKeys,
|
||||
filterChartData,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
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 (
|
||||
return (
|
||||
<PrometheusContext.Provider value={passedContext}>
|
||||
<Card
|
||||
className={style.remove_extra_padding}
|
||||
bodyStyle={{
|
||||
// padding 24
|
||||
minHeight: visibleHeight + 48,
|
||||
}}
|
||||
title={this.props.title}
|
||||
extra={this.renderExtra()}
|
||||
title={title}
|
||||
extra={extraRender}
|
||||
loading={isLoading}
|
||||
>
|
||||
<VisibleObserver style={{ width: '100%', height: visibleHeight }}>
|
||||
{(visible) => (visible ? this.props.renderContent(this.store) : null)}
|
||||
{(visible) =>
|
||||
visible ? (
|
||||
<PrometheusContext.Consumer>
|
||||
{(v) => renderContent(v)}
|
||||
</PrometheusContext.Consumer>
|
||||
) : null
|
||||
}
|
||||
</VisibleObserver>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
</PrometheusContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseFetch;
|
||||
BaseCard.defaultProps = {
|
||||
visibleHeight: 100,
|
||||
};
|
||||
|
||||
export default BaseCard;
|
||||
|
@ -12,63 +12,52 @@
|
||||
// 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 { merge } from 'lodash';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { Chart, Line, Tooltip } from 'bizcharts';
|
||||
import ArrowsAltOutlined from '@ant-design/icons/ArrowsAltOutlined';
|
||||
|
||||
import { ChartType, getXScale } from './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';
|
||||
} from './utils/baseProps';
|
||||
import BaseCard from './BaseCard';
|
||||
|
||||
const ChartCard = (props) => {
|
||||
const {
|
||||
constructorParams,
|
||||
params,
|
||||
currentRange,
|
||||
interval,
|
||||
chartProps,
|
||||
title,
|
||||
extra,
|
||||
isModal = false,
|
||||
BaseContentConfig = {},
|
||||
} = props;
|
||||
const { chartProps } = props;
|
||||
const renderContent = (contextValue) => {
|
||||
const {
|
||||
height,
|
||||
scale,
|
||||
chartType,
|
||||
toolTipProps = baseToolTipProps,
|
||||
} = chartProps;
|
||||
|
||||
const {
|
||||
height,
|
||||
scale,
|
||||
chartType,
|
||||
toolTipProps = baseToolTipProps,
|
||||
} = chartProps;
|
||||
const { data } = contextValue;
|
||||
|
||||
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;
|
||||
}
|
||||
scale.x = merge(
|
||||
{},
|
||||
getXScale(props.fetchDataParams.currentRange),
|
||||
scale.x || {}
|
||||
);
|
||||
|
||||
const renderContent = (store) => {
|
||||
let data = toJS(store.data);
|
||||
if (store.device) {
|
||||
data = data.filter((d) => d.device === store.device);
|
||||
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;
|
||||
}
|
||||
scale.x = merge({}, getXScale(props.currentRange), scale.x || {});
|
||||
|
||||
return (
|
||||
<Chart autoFit padding="auto" data={data} height={height} scale={scale}>
|
||||
<Line {...lineProps} />
|
||||
@ -77,53 +66,81 @@ const ChartCard = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContent = observer(() => (
|
||||
<div style={{ height: 520 }}>
|
||||
<BaseContent
|
||||
renderChartCards={(store) => (
|
||||
<ChartCard
|
||||
{...props}
|
||||
currentRange={store.currentRange}
|
||||
interval={store.interval}
|
||||
isModal
|
||||
const extra = () => {
|
||||
const {
|
||||
title,
|
||||
createFetchParams,
|
||||
handleDataParams,
|
||||
fetchDataParams,
|
||||
isModal = false,
|
||||
} = props;
|
||||
let defaultNode = {};
|
||||
const { params: fParams = {} } = fetchDataParams;
|
||||
const { instance, hostname, ...rest } = fParams;
|
||||
if (fParams) {
|
||||
if (instance) {
|
||||
defaultNode.instance = instance;
|
||||
} else if (hostname) {
|
||||
defaultNode.hostname = hostname;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.extra && props.extra()}
|
||||
{!isModal && (
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowsAltOutlined />}
|
||||
onClick={() => {
|
||||
Modal.info({
|
||||
icon: null,
|
||||
content: (function () {
|
||||
const BaseContent =
|
||||
require('./component/BaseContent').default;
|
||||
return (
|
||||
<BaseContent
|
||||
renderNodeSelect={false}
|
||||
defaultNode={{
|
||||
metric: defaultNode,
|
||||
}}
|
||||
visibleHeight={props.chartProps.height}
|
||||
chartConfig={{
|
||||
chartCardList: [
|
||||
{
|
||||
title,
|
||||
createFetchParams,
|
||||
handleDataParams,
|
||||
fetchDataParams: {
|
||||
params: rest,
|
||||
},
|
||||
chartProps,
|
||||
span: 24,
|
||||
isModal: true,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})(),
|
||||
width: 1200,
|
||||
okText: t('OK'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{...BaseContentConfig}
|
||||
renderNodeSelect={false}
|
||||
/>
|
||||
</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'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{...props}
|
||||
renderContent={renderContent}
|
||||
visibleHeight={height}
|
||||
visibleHeight={props.chartProps.height}
|
||||
extra={extra}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(ChartCard);
|
||||
export default ChartCard;
|
||||
|
@ -12,63 +12,123 @@
|
||||
// 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';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Spin } from 'antd';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
|
||||
@observer
|
||||
class BaseContent extends Component {
|
||||
static propTypes = {
|
||||
renderChartCards: PropTypes.func.isRequired,
|
||||
renderTimeRangeSelect: PropTypes.bool,
|
||||
renderNodeSelect: PropTypes.bool,
|
||||
fetchNodesFunc: PropTypes.func,
|
||||
};
|
||||
import {
|
||||
defaultOneHourAgo,
|
||||
getRange,
|
||||
} from 'components/PrometheusChart/utils/utils';
|
||||
import Charts from './Charts';
|
||||
import useIntervals from './hooks/useIntervals';
|
||||
import useRangeSelect from './hooks/useRangeSelect';
|
||||
import useNodeSelect from './hooks/useNodeSelect';
|
||||
import styles from '../style.less';
|
||||
import { defaultGetNodes } from '../utils/fetchNodes';
|
||||
import BaseContentContext from './context';
|
||||
|
||||
static defaultProps = {
|
||||
renderTimeRangeSelect: true,
|
||||
renderNodeSelect: true,
|
||||
};
|
||||
const BaseContent = (props) => {
|
||||
const {
|
||||
renderTimeRangeSelect,
|
||||
chartConfig,
|
||||
renderNodeSelect,
|
||||
fetchNodesFunc,
|
||||
defaultNode,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new BaseMonitorStore({
|
||||
fetchNodesFunc: this.props.fetchNodesFunc,
|
||||
});
|
||||
}
|
||||
const [node, Nodes, setNode, setNodes] = useNodeSelect(defaultNode);
|
||||
|
||||
componentDidMount() {
|
||||
const { renderNodeSelect } = this.props;
|
||||
const [range, Selector, groupIndex, setRange] = useRangeSelect(
|
||||
defaultOneHourAgo()
|
||||
);
|
||||
|
||||
const [interval, Intervals] = useIntervals(range);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [isFetchingNodes, setIsFetchingNodes] = useState(true);
|
||||
|
||||
const handleRefresh = async (refresh = false) => {
|
||||
setIsLoading(true);
|
||||
if (renderNodeSelect) {
|
||||
this.store.getNodes();
|
||||
setIsFetchingNodes(true);
|
||||
const ret = await fetchNodesFunc();
|
||||
setNodes(ret);
|
||||
if (!node) {
|
||||
setNode(ret[0]);
|
||||
}
|
||||
// 非自选时间段刷新时间
|
||||
if (refresh && groupIndex !== 4) {
|
||||
setRange(getRange(groupIndex));
|
||||
}
|
||||
setIsFetchingNodes(false);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
this.store.setLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { renderChartCards, renderTimeRangeSelect, renderNodeSelect } =
|
||||
this.props;
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
{renderTimeRangeSelect && (
|
||||
<TimeRangeSelect
|
||||
style={{ marginBottom: 24 }}
|
||||
store={this.store}
|
||||
renderNodeSelect={renderNodeSelect}
|
||||
const passedContextValue = {
|
||||
interval,
|
||||
range,
|
||||
node,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleRefresh();
|
||||
}, [interval, range]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 300);
|
||||
}, [node]);
|
||||
|
||||
return (
|
||||
<div className={styles.base_content_container}>
|
||||
<BaseContentContext.Provider value={passedContextValue}>
|
||||
{(renderTimeRangeSelect || renderNodeSelect) && (
|
||||
<Button
|
||||
type="default"
|
||||
icon={<SyncOutlined />}
|
||||
onClick={() => handleRefresh(true)}
|
||||
className={styles.refresh}
|
||||
/>
|
||||
)}
|
||||
{renderNodeSelect && (
|
||||
<NodeSelect style={{ marginBottom: 24 }} store={this.store} />
|
||||
{renderTimeRangeSelect && (
|
||||
<div className={styles.header}>
|
||||
<Selector />
|
||||
<Intervals />
|
||||
</div>
|
||||
)}
|
||||
{renderChartCards(this.store)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
{renderNodeSelect && (isFetchingNodes ? <Spin /> : <Nodes />)}
|
||||
{(renderNodeSelect && isFetchingNodes) ||
|
||||
(isLoading &&
|
||||
chartConfig?.chartCardList?.length !== 0 &&
|
||||
chartConfig?.topCardList?.length !== 0) ? null : (
|
||||
<Charts {...chartConfig} />
|
||||
)}
|
||||
{(renderNodeSelect && isFetchingNodes) || isLoading ? (
|
||||
<Spin />
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</BaseContentContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BaseContent.defaultProps = {
|
||||
renderNodeSelect: true,
|
||||
renderTimeRangeSelect: true,
|
||||
fetchNodesFunc: defaultGetNodes,
|
||||
defaultNode: undefined,
|
||||
};
|
||||
|
||||
export default BaseContent;
|
||||
|
146
src/components/PrometheusChart/component/Charts.jsx
Normal file
146
src/components/PrometheusChart/component/Charts.jsx
Normal file
@ -0,0 +1,146 @@
|
||||
// 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, { useContext } from 'react';
|
||||
import { get, merge } from 'lodash';
|
||||
|
||||
import { Col, Row } from 'antd';
|
||||
import { handleResponses } from '../utils/dataHandler';
|
||||
import styles from './styles.less';
|
||||
import BaseCard from '../BaseCard';
|
||||
import ChartCard from '../ChartCard';
|
||||
import BaseContentContext from './context';
|
||||
|
||||
const Charts = (props) => {
|
||||
const { baseTopCardProps, baseChartProps, topCardList, chartCardList } =
|
||||
props;
|
||||
|
||||
const ctx = useContext(BaseContentContext);
|
||||
|
||||
function renderTopCards() {
|
||||
return (
|
||||
<Row gutter={[16, 16]} style={{ width: '100%' }}>
|
||||
{topCardList.map((chartProps) => {
|
||||
const config = merge({}, baseTopCardProps, chartProps);
|
||||
const { span, fetchDataParams = {}, ...rest } = config;
|
||||
const colProps = {
|
||||
key: rest.title,
|
||||
};
|
||||
if (!span) {
|
||||
colProps.flex = 1;
|
||||
} else {
|
||||
colProps.span = span;
|
||||
}
|
||||
|
||||
const { params = {} } = fetchDataParams;
|
||||
|
||||
const newFetchDataParams = {
|
||||
currentRange: ctx.range,
|
||||
interval: ctx.interval,
|
||||
params,
|
||||
};
|
||||
if (ctx.node?.metric.hostname) {
|
||||
newFetchDataParams.params.hostname = ctx.node?.metric.hostname;
|
||||
} else if (ctx.node?.metric.instance) {
|
||||
newFetchDataParams.params.instance = ctx.node?.metric.instance;
|
||||
}
|
||||
return (
|
||||
<Col {...colProps}>
|
||||
<BaseCard {...rest} fetchDataParams={newFetchDataParams} />
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function renderChartCards() {
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartCardList.map((chartProps) => {
|
||||
const config = merge({}, baseChartProps, chartProps);
|
||||
const { span, fetchDataParams = {}, ...rest } = config;
|
||||
const colProps = {
|
||||
key: rest.title,
|
||||
};
|
||||
if (!span) {
|
||||
colProps.flex = 1;
|
||||
} else {
|
||||
colProps.span = span;
|
||||
}
|
||||
|
||||
const { params = {} } = fetchDataParams;
|
||||
|
||||
const newFetchDataParams = {
|
||||
currentRange: ctx.range,
|
||||
interval: ctx.interval,
|
||||
params,
|
||||
};
|
||||
if (ctx.node?.metric.hostname) {
|
||||
newFetchDataParams.params.hostname = ctx.node?.metric.hostname;
|
||||
} else if (ctx.node?.metric.instance) {
|
||||
newFetchDataParams.params.instance = ctx.node?.metric.instance;
|
||||
}
|
||||
return (
|
||||
<Col {...colProps}>
|
||||
<ChartCard {...rest} fetchDataParams={newFetchDataParams} />
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{topCardList.length !== 0 && <Col span={24}>{renderTopCards()}</Col>}
|
||||
{chartCardList.length !== 0 && <Col span={24}> {renderChartCards()}</Col>}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
Charts.defaultProps = {
|
||||
baseTopCardProps: {
|
||||
createFetchParams: {
|
||||
requestType: 'current',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
renderContent: ({ data }) => (
|
||||
<div className={styles.topContent}>{get(data, '[0].y', 0)}</div>
|
||||
),
|
||||
},
|
||||
baseChartProps: {
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
requestType: 'range',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 300,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
topCardList: [],
|
||||
chartCardList: [],
|
||||
};
|
||||
|
||||
export default Charts;
|
34
src/components/PrometheusChart/component/Intervals.jsx
Normal file
34
src/components/PrometheusChart/component/Intervals.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Select } from 'antd';
|
||||
import React, { useContext } from 'react';
|
||||
import BaseContentContext from './BaseContent';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Intervals = (props) => {
|
||||
const { intervals, setInterval } = props;
|
||||
const { interval } = useContext(BaseContentContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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={setInterval}>
|
||||
{intervals.map((d) => (
|
||||
<Option key={d.value} value={d.value}>
|
||||
{d.text}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Intervals;
|
@ -1,54 +0,0 @@
|
||||
// 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;
|
@ -1,181 +0,0 @@
|
||||
// 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;
|
14
src/components/PrometheusChart/component/context.js
Normal file
14
src/components/PrometheusChart/component/context.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
import { defaultOneHourAgo } from '../utils/utils';
|
||||
|
||||
const BaseContentContext = createContext({
|
||||
interval: 10,
|
||||
range: defaultOneHourAgo(),
|
||||
node: {
|
||||
metric: {
|
||||
hostname: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default BaseContentContext;
|
@ -0,0 +1,50 @@
|
||||
import { Select } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getInterval } from '../../utils/utils';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const useIntervals = (range) => {
|
||||
let intervals = getInterval(range);
|
||||
|
||||
const [interval, setInterval] = useState(intervals[0].value);
|
||||
|
||||
const handleIntervalChange = (e) => {
|
||||
setInterval(e);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
intervals = getInterval(range);
|
||||
handleIntervalChange(intervals[0].value);
|
||||
}, [range]);
|
||||
|
||||
const Intervals = () => (
|
||||
<>
|
||||
<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={handleIntervalChange}
|
||||
>
|
||||
{intervals.map((d) => (
|
||||
<Option key={d.value} value={d.value}>
|
||||
{d.text}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
);
|
||||
|
||||
return [interval, Intervals];
|
||||
};
|
||||
|
||||
export default useIntervals;
|
@ -0,0 +1,51 @@
|
||||
import { Select } from 'antd';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import styles from '../../style.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const useNodeSelect = (defaultNode) => {
|
||||
const [node, setNode] = useState(defaultNode);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
|
||||
const handleNodeChange = (e) => {
|
||||
const key = getKey();
|
||||
setNode(nodes.find((i) => i.metric[key] === e));
|
||||
};
|
||||
|
||||
const Nodes = () => {
|
||||
const key = getKey();
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<span style={{ color: 'black', fontSize: 14, fontWeight: 400 }}>
|
||||
Node:{' '}
|
||||
</span>
|
||||
<Select
|
||||
value={node.metric[key]}
|
||||
onChange={handleNodeChange}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
{nodes.map((item) => (
|
||||
<Option key={item.metric[key]} value={item.metric[key]}>
|
||||
{item.metric[key]}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return [node, Nodes, setNode, setNodes];
|
||||
|
||||
function getKey() {
|
||||
const hostname = get(node, 'metric.hostname', false);
|
||||
let key = 'instance';
|
||||
if (hostname) {
|
||||
key = 'hostname';
|
||||
}
|
||||
return key;
|
||||
}
|
||||
};
|
||||
|
||||
export default useNodeSelect;
|
@ -0,0 +1,94 @@
|
||||
import React, { useState } from 'react';
|
||||
import { DatePicker, Radio } from 'antd';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getRange } from 'components/PrometheusChart/utils/utils';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
function useRangeSelect(initialRange) {
|
||||
const [groupIndex, setGroupIndex] = useState(0);
|
||||
|
||||
const [range, setRange] = useState(initialRange);
|
||||
|
||||
const handleGroupChange = (e) => {
|
||||
const val = e.target.value;
|
||||
setGroupIndex(val);
|
||||
setRange(getRange(val));
|
||||
};
|
||||
|
||||
const handleRangePickerChange = (dates) => {
|
||||
setGroupIndex(4);
|
||||
setRange(dates);
|
||||
};
|
||||
|
||||
const Selector = () => (
|
||||
<Radio.Group
|
||||
value={groupIndex}
|
||||
onChange={handleGroupChange}
|
||||
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 }}>
|
||||
<RangePicker
|
||||
showTime={{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [
|
||||
moment('00:00:00', 'HH:mm:ss'),
|
||||
moment('00:00:00', 'HH:mm:ss'),
|
||||
],
|
||||
}}
|
||||
disabledDate={disabledDate}
|
||||
disabledTime={disableTime}
|
||||
onChange={handleRangePickerChange}
|
||||
value={range}
|
||||
bordered={false}
|
||||
allowClear={false}
|
||||
/>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
);
|
||||
|
||||
return [range, Selector, groupIndex, setRange];
|
||||
}
|
||||
|
||||
export default useRangeSelect;
|
||||
|
||||
function 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function disabledDate(current) {
|
||||
// Can not select days after today
|
||||
return current > moment().endOf('day');
|
||||
}
|
||||
|
||||
function filterRange(start, end) {
|
||||
const result = [];
|
||||
for (let i = start; i < end; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
return result;
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
.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;
|
||||
}
|
8
src/components/PrometheusChart/component/styles.less
Normal file
8
src/components/PrometheusChart/component/styles.less
Normal file
@ -0,0 +1,8 @@
|
||||
.topContent {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
// 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: {
|
||||
instance: '',
|
||||
},
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// 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 { clone, 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) {
|
||||
const formatedData = this.formatDataFn(
|
||||
data,
|
||||
this.typeKey,
|
||||
this.deviceKey,
|
||||
this.modifyKeys
|
||||
);
|
||||
this.data = clone(formatedData);
|
||||
if (
|
||||
isArray(formatedData) &&
|
||||
formatedData.length !== 0 &&
|
||||
formatedData[0].device
|
||||
) {
|
||||
const dv = new DataSet()
|
||||
.createView()
|
||||
.source(formatedData)
|
||||
.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;
|
||||
};
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
.remove_extra_padding {
|
||||
:global{
|
||||
:global {
|
||||
.ant-card-extra {
|
||||
padding: 0
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
@ -11,11 +11,26 @@
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items:center;
|
||||
align-items: center;
|
||||
|
||||
.ant-card-loading-content {
|
||||
width: 100%
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.base_content_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
padding: 20px;
|
||||
|
||||
.refresh {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,36 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { fetchPrometheus } from 'components/PrometheusChart/utils/utils';
|
||||
import metricDict from 'components/PrometheusChart/metricDict';
|
||||
import { get } from 'lodash';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
export const defaultGetNodes = async () => {
|
||||
const ret = await fetchPrometheus(
|
||||
get(METRICDICT, 'physicalNode.systemLoad.url[0]'),
|
||||
'current'
|
||||
);
|
||||
const {
|
||||
data: { result: results = [] },
|
||||
} = ret;
|
||||
if (results.length === 0) {
|
||||
return [
|
||||
{
|
||||
metric: {
|
||||
instance: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return results.map((result) => ({
|
||||
metric: {
|
||||
instance: result.metric.instance,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export const getMemcacheNodes = async () => {
|
||||
const ret = await fetchPrometheus(
|
||||
get(metricDict, 'memcacheService.currentConnections.url[0]'),
|
||||
get(METRICDICT, 'memcacheService.currentConnections.url[0]'),
|
||||
'current'
|
||||
);
|
||||
const {
|
||||
@ -43,7 +66,7 @@ export const getMemcacheNodes = async () => {
|
||||
|
||||
export const getRabbitMQNodes = async () => {
|
||||
const response = await fetchPrometheus(
|
||||
get(metricDict, 'rabbitMQService.serviceStatus.url[0]'),
|
||||
get(METRICDICT, 'rabbitMQService.serviceStatus.url[0]'),
|
||||
'current'
|
||||
);
|
||||
|
||||
@ -75,7 +98,7 @@ export const getRabbitMQNodes = async () => {
|
||||
|
||||
export const getMysqlNodes = async () => {
|
||||
const ret = await fetchPrometheus(
|
||||
get(metricDict, 'mysqlService.runningTime.url[0]'),
|
||||
get(METRICDICT, 'mysqlService.runningTime.url[0]'),
|
||||
'current'
|
||||
);
|
||||
const {
|
53
src/components/PrometheusChart/utils/index.js
Normal file
53
src/components/PrometheusChart/utils/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { get, clone, isArray } from 'lodash';
|
||||
import DataSet from '@antv/data-set';
|
||||
import { baseReturnFunc, fetchPrometheus, getRequestUrl } from './utils';
|
||||
|
||||
export function createFetchPrometheusClient(createParams) {
|
||||
const { requestType, metricKey } = createParams;
|
||||
|
||||
const queryParams = get(METRICDICT, metricKey);
|
||||
|
||||
return async function ({ params = {}, currentRange, interval }) {
|
||||
const promises = queryParams.url.map((u, idx) => {
|
||||
// 按顺序取聚合函数
|
||||
const finalFormatFunc =
|
||||
(queryParams.finalFormatFunc || [])[idx] || baseReturnFunc;
|
||||
// 按顺序获取基础参数
|
||||
const baseParams = (queryParams.baseParams || [])[idx] || {};
|
||||
const finalUrl = getRequestUrl(u, params, finalFormatFunc, baseParams);
|
||||
return fetchPrometheus(finalUrl, requestType, currentRange, interval);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
};
|
||||
}
|
||||
|
||||
export function createDataHandler(params) {
|
||||
const { formatDataFn, typeKey, deviceKey, modifyKeys } = params;
|
||||
|
||||
return (data) => {
|
||||
const formatedData = formatDataFn(data, typeKey, deviceKey, modifyKeys);
|
||||
const retData = clone(formatedData);
|
||||
let device = '';
|
||||
let devices = [];
|
||||
if (
|
||||
isArray(formatedData) &&
|
||||
formatedData.length !== 0 &&
|
||||
formatedData[0].device
|
||||
) {
|
||||
const dv = new DataSet()
|
||||
.createView()
|
||||
.source(formatedData)
|
||||
.transform({
|
||||
type: 'partition',
|
||||
groupBy: ['device'],
|
||||
});
|
||||
devices = Object.keys(dv.rows).map((d) => d.slice(1, d.length));
|
||||
device = devices[0];
|
||||
}
|
||||
return {
|
||||
retData,
|
||||
device,
|
||||
devices,
|
||||
};
|
||||
};
|
||||
}
|
@ -12,60 +12,10 @@
|
||||
// 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;
|
||||
}
|
||||
import moment from 'moment';
|
||||
import client from 'src/client';
|
||||
import { get, isArray } from 'lodash';
|
||||
|
||||
export const ChartType = {
|
||||
ONELINE: 'oneline',
|
||||
@ -95,18 +45,78 @@ export const getXScale = (timeRange) => {
|
||||
|
||||
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');
|
||||
export function fetchPrometheus(
|
||||
query,
|
||||
getRangeType = 'range',
|
||||
currentRange = [],
|
||||
interval = 10
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 给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)}}`;
|
||||
}
|
||||
|
||||
// 1 hour ago - now
|
||||
export const defaultOneHourAgo = () => [
|
||||
moment().subtract(1, 'hours'),
|
||||
moment(),
|
||||
];
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
const maskAndTicketCountDict = [
|
||||
{
|
||||
@ -188,30 +198,15 @@ export const range2IntervalsDict = [
|
||||
],
|
||||
];
|
||||
|
||||
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()];
|
||||
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');
|
||||
});
|
||||
};
|
||||
|
@ -22,10 +22,12 @@ import zhCN from 'antd/es/locale/zh_CN';
|
||||
import enUS from 'antd/es/locale/en_US';
|
||||
import globalRootStore from 'stores/root';
|
||||
import PageLoading from 'components/PageLoading';
|
||||
import metricDict from 'resources/metricDict';
|
||||
import i18n from './i18n';
|
||||
import App from './App';
|
||||
|
||||
window.t = i18n.t;
|
||||
window.METRICDICT = metricDict;
|
||||
|
||||
const store = globalRootStore;
|
||||
const browserHistory = createBrowserHistory();
|
||||
|
@ -61,7 +61,7 @@ const Services = (props) => {
|
||||
)}
|
||||
</Col>
|
||||
<Col className={styles.title} span={6}>
|
||||
{it.hostname}
|
||||
{it.hostname || it.host}
|
||||
</Col>
|
||||
<Col className={styles.status} span={6}>
|
||||
<span>{t('Current Status')}</span>
|
||||
|
@ -18,13 +18,14 @@ import { OpenstackServiceStore } from 'stores/prometheus/openstack-service';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import Services from './Services';
|
||||
import styles from '../PhysicalNode/index.less';
|
||||
import styles from './index.less';
|
||||
|
||||
@observer
|
||||
class OpenstackService extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new OpenstackServiceStore();
|
||||
const { Store = OpenstackServiceStore } = props;
|
||||
this.store = new Store();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -67,18 +68,14 @@ class OpenstackService extends Component {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.outer}>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.header}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<SyncOutlined />}
|
||||
onClick={this.handleRefresh}
|
||||
/>
|
||||
{/* <NodeSelect style={{ display: 'inline', marginLeft: 20 }} store={this.store} /> */}
|
||||
<Services serviceMap={serviceMap} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<SyncOutlined />}
|
||||
onClick={this.handleRefresh}
|
||||
/>
|
||||
{/* <NodeSelect style={{ display: 'inline', marginLeft: 20 }} store={this.store} /> */}
|
||||
<Services serviceMap={serviceMap} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -5,9 +5,8 @@
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
|
||||
.list {
|
||||
background-color: #FFFFFF;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.05);
|
||||
border: none;
|
||||
|
||||
@ -30,3 +29,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -1,151 +0,0 @@
|
||||
// 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 { Col, Row } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { merge } from 'lodash';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
|
||||
@observer
|
||||
class Charts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = props.store;
|
||||
}
|
||||
|
||||
renderChartCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 300,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Current Connections'),
|
||||
constructorParams: {
|
||||
metricKey: 'memcacheService.currentConnections',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Current Connections'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Connections'),
|
||||
constructorParams: {
|
||||
metricKey: 'memcacheService.totalConnections',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Total Connections'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Read And Write'),
|
||||
constructorParams: {
|
||||
metricKey: 'memcacheService.readWriteBytesTotal',
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Evictions'),
|
||||
constructorParams: {
|
||||
metricKey: 'memcacheService.evictions',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Evictions'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Items in Cache'),
|
||||
constructorParams: {
|
||||
metricKey: 'memcacheService.itemsInCache',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Items in Cache'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<ChartCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
BaseContentConfig={this.props.BaseContentConfig}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.store.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return <>{this.renderChartCards()}</>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts;
|
@ -13,29 +13,91 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import { getMemcacheNodes } from '../util';
|
||||
import Charts from './Charts';
|
||||
import { getMemcacheNodes } from 'components/PrometheusChart/utils/fetchNodes';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
|
||||
const Memcache = () => {
|
||||
function renderChartCards(store) {
|
||||
return (
|
||||
<Charts
|
||||
store={store}
|
||||
BaseContentConfig={{
|
||||
fetchNodesFunc: getMemcacheNodes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const chartCardList = [
|
||||
{
|
||||
title: t('Current Connections'),
|
||||
createFetchParams: {
|
||||
metricKey: 'memcacheService.currentConnections',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Current Connections'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Connections'),
|
||||
createFetchParams: {
|
||||
metricKey: 'memcacheService.totalConnections',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Total Connections'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Read And Write'),
|
||||
createFetchParams: {
|
||||
metricKey: 'memcacheService.readWriteBytesTotal',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Evictions'),
|
||||
createFetchParams: {
|
||||
metricKey: 'memcacheService.evictions',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Evictions'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Items in Cache'),
|
||||
createFetchParams: {
|
||||
metricKey: 'memcacheService.itemsInCache',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Items in Cache'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BaseContent
|
||||
renderChartCards={renderChartCards}
|
||||
fetchNodesFunc={getMemcacheNodes}
|
||||
/>
|
||||
);
|
||||
export const chartConfig = {
|
||||
chartCardList,
|
||||
};
|
||||
|
||||
export default observer(Memcache);
|
||||
export default () => (
|
||||
<BaseContent chartConfig={chartConfig} fetchNodesFunc={getMemcacheNodes} />
|
||||
);
|
||||
|
@ -1,202 +0,0 @@
|
||||
// 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 { Col, Row } from 'antd';
|
||||
import styles from 'pages/monitor/containers/StorageCluster/index.less';
|
||||
import { observer } from 'mobx-react';
|
||||
import { formatUsedTime } from 'utils/index';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { get, merge } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
|
||||
@observer
|
||||
class Charts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = props.store;
|
||||
}
|
||||
|
||||
renderTopCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 55,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
{get(store, 'data[0].y', 0)}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Running Time'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.runningTime',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
{/* 转化为毫秒 */}
|
||||
{formatUsedTime(get(store.data[0], 'y', 0) * 1000)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Connected Threads'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.connectedThreads',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Running Threads'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.runningThreads',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Slow Query'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.slowQuery',
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
renderChartCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 300,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Threads Activity Trends'),
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.threadsActivityTrends_connected',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Threads Activity Trends'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('MySQL Actions'),
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.mysqlActions',
|
||||
modifyKeys: [t('delete'), t('insert'), t('update')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('MySQL Actions'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Slow Query'),
|
||||
constructorParams: {
|
||||
metricKey: 'mysqlService.slowQueryChart',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Slow Query'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<ChartCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
BaseContentConfig={this.props.BaseContentConfig}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.store.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>{this.renderTopCards()}</Col>
|
||||
<Col span={24}>{this.renderChartCards()}</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts;
|
@ -13,29 +13,104 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import { getMysqlNodes } from '../util';
|
||||
import Charts from './Charts';
|
||||
import { getMysqlNodes } from 'components/PrometheusChart/utils/fetchNodes';
|
||||
import { formatUsedTime } from 'src/utils';
|
||||
import styles from 'components/PrometheusChart/component/styles.less';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
|
||||
const Mysql = () => {
|
||||
function renderChartCards(store) {
|
||||
return (
|
||||
<Charts
|
||||
store={store}
|
||||
BaseContentConfig={{
|
||||
fetchNodesFunc: getMysqlNodes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const topCardList = [
|
||||
{
|
||||
title: t('Running Time'),
|
||||
span: 6,
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.runningTime',
|
||||
},
|
||||
renderContent: ({ data }) => (
|
||||
<div className={styles.topContent}>
|
||||
{/* 转化为毫秒 */}
|
||||
{formatUsedTime(get(data, '[0].y', 0) * 1000)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Connected Threads'),
|
||||
span: 6,
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.connectedThreads',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Running Threads'),
|
||||
span: 6,
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.runningThreads',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Slow Query'),
|
||||
span: 6,
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.slowQuery',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BaseContent
|
||||
renderChartCards={renderChartCards}
|
||||
fetchNodesFunc={getMysqlNodes}
|
||||
/>
|
||||
);
|
||||
const chartCardList = [
|
||||
{
|
||||
title: t('Threads Activity Trends'),
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.threadsActivityTrends_connected',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Threads Activity Trends'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('MySQL Actions'),
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.mysqlActions',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('delete'), t('insert'), t('update')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('MySQL Actions'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Slow Query'),
|
||||
createFetchParams: {
|
||||
metricKey: 'mysqlService.slowQueryChart',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Slow Query'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const chartConfig = {
|
||||
topCardList,
|
||||
chartCardList,
|
||||
};
|
||||
|
||||
export default observer(Mysql);
|
||||
export default () => (
|
||||
<BaseContent chartConfig={chartConfig} fetchNodesFunc={getMysqlNodes} />
|
||||
);
|
||||
|
@ -1,232 +0,0 @@
|
||||
// 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 { Col, Row } from 'antd';
|
||||
import styles from 'pages/monitor/containers/StorageCluster/index.less';
|
||||
import { observer } from 'mobx-react';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { get, merge } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
|
||||
@observer
|
||||
class Charts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = props.store;
|
||||
}
|
||||
|
||||
renderTopCards() {
|
||||
const baseConfig = {
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 55,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
{get(store, 'data[0].y', 0)}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Server Status'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.serviceStatus',
|
||||
formatDataFn: (resps) => {
|
||||
const tmp = {
|
||||
up: 0,
|
||||
down: 0,
|
||||
};
|
||||
const result = get(resps[0], 'data.result', []);
|
||||
result.forEach((r) => {
|
||||
parseInt(r.value[1], 10) === 1 ? (tmp.up += 1) : (tmp.down += 1);
|
||||
});
|
||||
return tmp;
|
||||
},
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<Row style={{ width: '100%', textAlign: 'center' }}>
|
||||
<Col span={12}>{data.up + t('Up')}</Col>
|
||||
<Col span={12}>{data.down + t('Down')}</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Connected Threads'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.totalConnections',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Queues'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.totalQueues',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Exchanges'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.totalExchanges',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Consumers'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.totalConsumers',
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col flex={1} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
renderChartCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 300,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Published Out'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.publishedOut',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Published Out'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Published In'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.publishedIn',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Published In'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: t('Total Message'),
|
||||
// constructorParams: {
|
||||
// metricKey: 'rabbitMQService.totalMessage',
|
||||
// },
|
||||
// chartProps: {
|
||||
// chartType: ChartType.ONELINE,
|
||||
// scale: {
|
||||
// y: {
|
||||
// alias: t('Total Message'),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: t('Channel'),
|
||||
constructorParams: {
|
||||
metricKey: 'rabbitMQService.channel',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Channel'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<ChartCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
BaseContentConfig={this.props.BaseContentConfig}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.store.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>{this.renderTopCards()}</Col>
|
||||
<Col span={24}>{this.renderChartCards()}</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts;
|
@ -13,29 +13,121 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { get } from 'lodash';
|
||||
import { Col, Row } from 'antd';
|
||||
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import Charts from './Charts';
|
||||
import { getRabbitMQNodes } from '../util';
|
||||
import { getRabbitMQNodes } from 'components/PrometheusChart/utils/fetchNodes';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
|
||||
const RabbitMQ = () => {
|
||||
function renderChartCards(store) {
|
||||
return (
|
||||
<Charts
|
||||
store={store}
|
||||
BaseContentConfig={{
|
||||
fetchNodesFunc: getRabbitMQNodes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import styles from 'components/PrometheusChart/component/styles.less';
|
||||
|
||||
return (
|
||||
<BaseContent
|
||||
renderChartCards={renderChartCards}
|
||||
fetchNodesFunc={getRabbitMQNodes}
|
||||
/>
|
||||
);
|
||||
const topCardList = [
|
||||
{
|
||||
title: t('Server Status'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.serviceStatus',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (resps) => {
|
||||
const tmp = {
|
||||
up: 0,
|
||||
down: 0,
|
||||
};
|
||||
const result = get(resps[0], 'data.result', []);
|
||||
result.forEach((r) => {
|
||||
parseInt(r.value[1], 10) === 1 ? (tmp.up += 1) : (tmp.down += 1);
|
||||
});
|
||||
return tmp;
|
||||
},
|
||||
},
|
||||
renderContent: ({ data }) => {
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<Row style={{ width: '100%', textAlign: 'center' }}>
|
||||
<Col span={12}>{data.up + t('Up')}</Col>
|
||||
<Col span={12}>{data.down + t('Down')}</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Connected Threads'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.totalConnections',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Queues'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.totalQueues',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Exchanges'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.totalExchanges',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Consumers'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.totalConsumers',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const chartCardList = [
|
||||
{
|
||||
title: t('Published Out'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.publishedOut',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Published Out'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Published In'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.publishedIn',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Published In'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Channel'),
|
||||
createFetchParams: {
|
||||
metricKey: 'rabbitMQService.channel',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.ONELINE,
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Channel'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const chartConfig = {
|
||||
topCardList,
|
||||
chartCardList,
|
||||
};
|
||||
|
||||
export default observer(RabbitMQ);
|
||||
export default () => (
|
||||
<BaseContent chartConfig={chartConfig} fetchNodesFunc={getRabbitMQNodes} />
|
||||
);
|
||||
|
@ -1,55 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { Empty, Row } from 'antd';
|
||||
import { Chart, Line, Tooltip } from 'bizcharts';
|
||||
import { observer } from 'mobx-react';
|
||||
import styles from '../../../index.less';
|
||||
|
||||
export default observer(({ data }) => {
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<Row justify="space-between">
|
||||
<span>{t('Last week alarm trend')}</span>
|
||||
<span>{t('time / 24h')}</span>
|
||||
</Row>
|
||||
<Row
|
||||
justify="center"
|
||||
align="middle"
|
||||
style={{ height: 272, paddingTop: 10 }}
|
||||
>
|
||||
{data.length === 0 ? <Empty /> : <Charts data={data} />}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
function Charts({ data }) {
|
||||
return (
|
||||
<Chart
|
||||
padding={[10, 20, 50, 50]}
|
||||
autoFit
|
||||
data={data}
|
||||
scale={{
|
||||
count: {
|
||||
nice: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Line position="date*count" />
|
||||
<Tooltip showCrosshairs lock />
|
||||
</Chart>
|
||||
);
|
||||
}
|
@ -13,19 +13,25 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Col, Row, Spin } from 'antd';
|
||||
import { Col, Empty, Row, Spin } from 'antd';
|
||||
import styles from 'pages/monitor/containers/Overview/index.less';
|
||||
import FetchPrometheusStore from 'components/PrometheusChart/store/FetchPrometheusStore';
|
||||
import { handleResponse } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import moment from 'moment';
|
||||
import AlertChart from './components/AlertChart';
|
||||
import {
|
||||
createDataHandler,
|
||||
createFetchPrometheusClient,
|
||||
} from 'components/PrometheusChart/utils';
|
||||
import { Chart, Line, Tooltip } from 'bizcharts';
|
||||
|
||||
const STEP = 15;
|
||||
|
||||
const Index = function () {
|
||||
const store = new FetchPrometheusStore({
|
||||
const fetchData = createFetchPrometheusClient({
|
||||
requestType: 'range',
|
||||
metricKey: 'monitorOverview.alertInfo',
|
||||
});
|
||||
|
||||
const dataHandler = createDataHandler({
|
||||
formatDataFn: (responses, typeKey, deviceKey, modifyKeys) => {
|
||||
const ret = [];
|
||||
responses.forEach((response, idx) => {
|
||||
@ -45,13 +51,12 @@ const Index = function () {
|
||||
const end = moment();
|
||||
const start = moment().startOf('day');
|
||||
setIsLoading(true);
|
||||
store
|
||||
.fetchData({
|
||||
interval: STEP,
|
||||
currentRange: [start, end],
|
||||
})
|
||||
fetchData({
|
||||
interval: STEP,
|
||||
currentRange: [start, end],
|
||||
})
|
||||
.then((d) => {
|
||||
const [cpuData, memoryData] = d;
|
||||
const [cpuData, memoryData] = dataHandler(d).retData;
|
||||
const newCpuCount = cpuData.reduce(
|
||||
(pre, cur, idx) =>
|
||||
idx > 0 && cur.x - cpuData[idx - 1].x > STEP ? pre + 1 : pre,
|
||||
@ -109,4 +114,40 @@ function build7DaysData() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function AlertChart({ data }) {
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<Row justify="space-between">
|
||||
<span>{t('Last week alarm trend')}</span>
|
||||
<span>{t('time / 24h')}</span>
|
||||
</Row>
|
||||
<Row
|
||||
justify="center"
|
||||
align="middle"
|
||||
style={{ height: 272, paddingTop: 10 }}
|
||||
>
|
||||
{data.length === 0 ? <Empty /> : <Charts data={data} />}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Charts({ data }) {
|
||||
return (
|
||||
<Chart
|
||||
padding={[10, 20, 50, 50]}
|
||||
autoFit
|
||||
data={data}
|
||||
scale={{
|
||||
count: {
|
||||
nice: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Line position="date*count" />
|
||||
<Tooltip showCrosshairs lock />
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
|
@ -1,171 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { Col, Progress, Row } from 'antd';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { get, merge } from 'lodash';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
import { computePercentage } from 'utils/index';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import styles from '../../index.less';
|
||||
|
||||
const ClusterCard = (props) => {
|
||||
const { store: propStore } = props;
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 55,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>{store.data}</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Physical CPU Usage'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.physicalCPUUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const used = get(data[0], 'y', 0);
|
||||
const total = get(data[1], 'y', 0);
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<div>
|
||||
<Row style={{ alignItems: 'baseline', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(used, total)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Ram'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.physicalMemoryUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'memory');
|
||||
const total = getSuitableValue(totalValue, 'memory');
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<div>
|
||||
<Row style={{ alignItems: 'baseline', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Physical Storage Usage'),
|
||||
span: 24,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.physicalStorageUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
const progressPercentage = computePercentage(usedValue, totalValue);
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Row style={{ justifyContent: 'flex-end', height: '50%' }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginRight: 32,
|
||||
}}
|
||||
>
|
||||
{`${t('Used')} ${used} / ${t('Total')} ${total}`}
|
||||
</span>
|
||||
</Row>
|
||||
<Row style={{ height: '50%' }}>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={progressPercentage}
|
||||
strokeColor={progressPercentage > 80 ? '#FAAD14' : '#1890FF'}
|
||||
showInfo={progressPercentage !== 100}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={propStore.currentRange}
|
||||
interval={propStore.interval}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClusterCard;
|
@ -1,65 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import CircleChart from 'components/PrometheusChart/CircleWithRightLegend';
|
||||
import { get } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
|
||||
const ClusterChart = observer((props) => {
|
||||
const { store: propStore } = props;
|
||||
|
||||
const config = {
|
||||
title: t('Compute Node status'),
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: (responses) => {
|
||||
const status = [
|
||||
{
|
||||
type: 'up',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
type: 'down',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
const result = get(responses[0], 'data.result', []);
|
||||
result.forEach((sta) => {
|
||||
const idx = sta.metric.adminState === 'enabled' ? 0 : 1;
|
||||
status[idx].value += parseInt(sta.value[1], 10);
|
||||
});
|
||||
return status;
|
||||
},
|
||||
metricKey: 'monitorOverview.computeNodeStatus',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div style={{ height: 218 }}>
|
||||
<CircleChart data={store.data} />
|
||||
</div>
|
||||
),
|
||||
visibleHeight: 230,
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseCard
|
||||
{...config}
|
||||
currentRange={propStore.currentRange}
|
||||
interval={propStore.interval}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default ClusterChart;
|
@ -1,155 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { Col, Row } from 'antd';
|
||||
import { computePercentage } from 'utils/index';
|
||||
import {
|
||||
cephStatusColorMap,
|
||||
cephStatusMap,
|
||||
getSuitableValue,
|
||||
} from 'resources/monitoring';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { get, merge } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import styles from '../../index.less';
|
||||
|
||||
const StorageClusterCard = (props) => {
|
||||
const { store: propStore } = props;
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 65,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>{JSON.stringify(store.data)}</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Storage Cluster Status'),
|
||||
span: 24,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.cephHealthStatus',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const data = get(store.data, 'y', 0);
|
||||
return (
|
||||
<div
|
||||
className={styles.topContent}
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 600,
|
||||
color: cephStatusColorMap[data],
|
||||
height: 65,
|
||||
}}
|
||||
>
|
||||
{cephStatusMap[data]}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster Usage'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.cephStorageUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<div>
|
||||
<Row style={{ alignItems: 'baseline', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Disk allocation (GB)'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.cephStorageAllocate',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const totalValue = parseFloat(get(data[1], 'y', 0).toFixed(2));
|
||||
const usedValue = parseFloat(
|
||||
(totalValue - get(data[0], 'y', 0)).toFixed(2)
|
||||
);
|
||||
return (
|
||||
<div className={styles.topContent} style={{ height: 55 }}>
|
||||
<div>
|
||||
<Row style={{ alignItems: 'baseline', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${usedValue} GB / ${totalValue} GB`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={propStore.currentRange}
|
||||
interval={propStore.interval}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageClusterCard;
|
@ -1,57 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
|
||||
const StorageClusterChart = observer((props) => {
|
||||
const { store: propStore } = props;
|
||||
|
||||
const config = {
|
||||
title: t('Storage Cluster IOPS'),
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
metricKey: 'monitorOverview.cephStorageClusterIOPS',
|
||||
formatDataFn: handleResponses,
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
height: 250,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
visibleHeight: 230,
|
||||
};
|
||||
|
||||
return (
|
||||
<ChartCard
|
||||
{...config}
|
||||
currentRange={propStore.currentRange}
|
||||
interval={propStore.interval}
|
||||
BaseContentConfig={{
|
||||
...props.BaseContentConfig,
|
||||
renderTimeRangeSelect: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default StorageClusterChart;
|
@ -1,187 +0,0 @@
|
||||
// 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 from 'react';
|
||||
import { Col, Progress, Row, Tabs } from 'antd';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { get, merge } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import { Chart, Interval } from 'bizcharts';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
import styles from '../../index.less';
|
||||
|
||||
const renderTopProgress = (store) => {
|
||||
const { data } = store;
|
||||
return (
|
||||
<Row style={{ height: '100%' }}>
|
||||
{data.map((d) => {
|
||||
const percentage = get(d, 'y', 0);
|
||||
const percentageColor = percentage > 80 ? '#FAAD14' : '#1890FF';
|
||||
return (
|
||||
<Col span={24} key={d.type}>
|
||||
<div>{d.type}</div>
|
||||
<Progress
|
||||
strokeColor={percentageColor}
|
||||
percent={percentage}
|
||||
style={{ marginBottom: 4 }}
|
||||
showInfo={percentage !== 100}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTopColumnExtra = (store) => {
|
||||
const { modifyKeys } = store;
|
||||
return (
|
||||
<Tabs
|
||||
className={styles.tabs}
|
||||
defaultActiveKey={modifyKeys[0]}
|
||||
onChange={(key) => store.handleDeviceChange(key)}
|
||||
>
|
||||
{modifyKeys.map((k) => (
|
||||
<Tabs.TabPane tab={k} key={k} />
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTopColumnChart = (store, scale = { y: {} }) => {
|
||||
const { data, modifyKeys } = store;
|
||||
return (
|
||||
<div>
|
||||
<Chart
|
||||
autoFit
|
||||
data={data.filter((d) => d.type === (store.device || modifyKeys[0]))}
|
||||
height={198}
|
||||
scale={{
|
||||
y: {
|
||||
nice: true,
|
||||
...scale.y,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Interval position="x*y" size={20} />
|
||||
</Chart>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TopCard = (props) => {
|
||||
const { store: propStore } = props;
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 200,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>{store.data}</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Host CPU Usage'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.topHostCPUUsage',
|
||||
typeKey: 'instance',
|
||||
},
|
||||
renderContent: renderTopProgress,
|
||||
},
|
||||
{
|
||||
title: t('Host Disk Average IOPS'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.topHostDiskIOPS',
|
||||
formatDataFn: (reps, tk, dk, mk) => {
|
||||
const data = [];
|
||||
reps.forEach((ret, resIdx) => {
|
||||
(ret.data.result || []).forEach((d) => {
|
||||
data.push({
|
||||
x: d.metric.instance,
|
||||
y: parseFloat(get(d, 'value[1]', 0)),
|
||||
type: mk[resIdx],
|
||||
});
|
||||
});
|
||||
});
|
||||
return data;
|
||||
},
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
extra: renderTopColumnExtra,
|
||||
renderContent: renderTopColumnChart,
|
||||
},
|
||||
{
|
||||
title: t('Host Memory Usage'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.topHostMemoryUsage',
|
||||
typeKey: 'instance',
|
||||
},
|
||||
renderContent: renderTopProgress,
|
||||
},
|
||||
{
|
||||
title: t('Host Average Network IO'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'monitorOverview.topHostInterface',
|
||||
formatDataFn: (reps, tk, dk, mk) => {
|
||||
const data = [];
|
||||
reps.forEach((ret, resIdx) => {
|
||||
(ret.data.result || []).forEach((d) => {
|
||||
data.push({
|
||||
x: d.metric.instance,
|
||||
y: parseFloat(get(d, 'value[1]', 0)),
|
||||
type: mk[resIdx],
|
||||
});
|
||||
});
|
||||
});
|
||||
return data;
|
||||
},
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
},
|
||||
extra: renderTopColumnExtra,
|
||||
renderContent: (store) =>
|
||||
renderTopColumnChart(store, {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
}),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, title, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
title={`${title} TOP5`}
|
||||
currentRange={propStore.currentRange}
|
||||
interval={propStore.interval}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopCard;
|
@ -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 from 'react';
|
||||
import { Col, Progress, Row, Tabs } from 'antd';
|
||||
import { get } from 'lodash';
|
||||
import { Chart, Interval } from 'bizcharts';
|
||||
import styles from '../../index.less';
|
||||
|
||||
export const renderTopProgress = ({ data }) => {
|
||||
return (
|
||||
<Row style={{ height: '100%' }}>
|
||||
{data.map((d) => {
|
||||
const percentage = get(d, 'y', 0);
|
||||
const percentageColor = percentage > 80 ? '#FAAD14' : '#1890FF';
|
||||
return (
|
||||
<Col span={24} key={d.type}>
|
||||
<div>{d.type}</div>
|
||||
<Progress
|
||||
strokeColor={percentageColor}
|
||||
percent={percentage}
|
||||
style={{ marginBottom: 4 }}
|
||||
showInfo={percentage !== 100}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderTopColumnExtra = ({ modifyKeys, filterChartData }) => {
|
||||
return (
|
||||
<Tabs
|
||||
className={styles.tabs}
|
||||
defaultActiveKey={modifyKeys[0]}
|
||||
onChange={(key) => filterChartData((i) => i.type === key)}
|
||||
>
|
||||
{modifyKeys.map((k) => (
|
||||
<Tabs.TabPane tab={k} key={k} />
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderTopColumnChart = ({ data, modifyKeys }) => {
|
||||
return (
|
||||
<Chart
|
||||
autoFit
|
||||
data={
|
||||
data.length <= 5 ? data : data.filter((d) => d.type === modifyKeys[0])
|
||||
}
|
||||
height={198}
|
||||
scale={{
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Interval position="x*y" size={20} />
|
||||
</Chart>
|
||||
);
|
||||
};
|
400
src/pages/monitor/containers/Overview/config.jsx
Normal file
400
src/pages/monitor/containers/Overview/config.jsx
Normal file
@ -0,0 +1,400 @@
|
||||
import React from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { Progress, Row } from 'antd';
|
||||
import { computePercentage } from 'src/utils';
|
||||
import {
|
||||
cephStatusColorMap,
|
||||
cephStatusMap,
|
||||
getSuitableValue,
|
||||
} from 'resources/monitoring';
|
||||
import CircleChart from 'components/PrometheusChart/CircleWithRightLegend';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import {
|
||||
renderTopColumnChart,
|
||||
renderTopColumnExtra,
|
||||
renderTopProgress,
|
||||
} from './components/Tops';
|
||||
import styles from './index.less';
|
||||
|
||||
export const physicalNodeLeftTopCardList = [
|
||||
{
|
||||
title: t('Physical CPU Usage'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.physicalCPUUsage',
|
||||
},
|
||||
renderContent: ({ data }) => {
|
||||
const used = get(data[0], 'y', 0);
|
||||
const total = get(data[1], 'y', 0);
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(used, total)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Total Ram'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.physicalMemoryUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'memory');
|
||||
const total = getSuitableValue(totalValue, 'memory');
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Physical Storage Usage'),
|
||||
span: 24,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.physicalStorageUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
const progressPercentage = computePercentage(usedValue, totalValue);
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
height: '50%',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginRight: 32,
|
||||
}}
|
||||
>
|
||||
{`${t('Used')} ${used} / ${t('Total')} ${total}`}
|
||||
</span>
|
||||
</Row>
|
||||
<Row style={{ height: '50%' }}>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={progressPercentage}
|
||||
strokeColor={progressPercentage > 80 ? '#FAAD14' : '#1890FF'}
|
||||
showInfo={progressPercentage !== 100}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const physicalNodeRightTopCardList = [
|
||||
{
|
||||
visibleHeight: 319,
|
||||
createFetchParams: {
|
||||
requestType: 'current',
|
||||
metricKey: 'monitorOverview.computeNodeStatus',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (responses) => {
|
||||
const status = [
|
||||
{
|
||||
type: 'up',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
type: 'down',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
const result = get(responses[0], 'data.result', []);
|
||||
result.forEach((sta) => {
|
||||
const idx = sta.metric.adminState === 'enabled' ? 0 : 1;
|
||||
status[idx].value += parseInt(sta.value[1], 10);
|
||||
});
|
||||
return status;
|
||||
},
|
||||
},
|
||||
title: t('Compute Node status'),
|
||||
renderContent: ({ data }) => (
|
||||
<div style={{ height: 309 }}>
|
||||
<CircleChart data={data} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const topCardList = [
|
||||
{
|
||||
title: t('Host CPU Usage'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.topHostCPUUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: 'instance',
|
||||
},
|
||||
renderContent: renderTopProgress,
|
||||
},
|
||||
{
|
||||
title: t('Host Disk Average IOPS'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.topHostDiskIOPS',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (reps, tk, dk, mk) => {
|
||||
const data = [];
|
||||
reps.forEach((ret, resIdx) => {
|
||||
(ret.data.result || []).forEach((d) => {
|
||||
data.push({
|
||||
x: d.metric.instance,
|
||||
y: parseFloat(get(d, 'value[1]', 0)),
|
||||
type: mk[resIdx],
|
||||
});
|
||||
});
|
||||
});
|
||||
return data;
|
||||
},
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
extra: renderTopColumnExtra,
|
||||
renderContent: renderTopColumnChart,
|
||||
},
|
||||
{
|
||||
title: t('Host Memory Usage'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.topHostMemoryUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: 'instance',
|
||||
},
|
||||
renderContent: renderTopProgress,
|
||||
},
|
||||
{
|
||||
title: t('Host Average Network IO'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.topHostInterface',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (reps, tk, dk, mk) => {
|
||||
const data = [];
|
||||
reps.forEach((ret, resIdx) => {
|
||||
(ret.data.result || []).forEach((d) => {
|
||||
data.push({
|
||||
x: d.metric.instance,
|
||||
y: parseFloat(get(d, 'value[1]', 0)),
|
||||
type: mk[resIdx],
|
||||
});
|
||||
});
|
||||
});
|
||||
return data;
|
||||
},
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
},
|
||||
extra: renderTopColumnExtra,
|
||||
renderContent: (p) => {
|
||||
const Cmp = renderTopColumnChart(p);
|
||||
return React.cloneElement(Cmp, {
|
||||
...Cmp.props,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const storageLeftCardList = [
|
||||
{
|
||||
title: t('Storage Cluster Status'),
|
||||
span: 24,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.cephHealthStatus',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const data = get(store.data, 'y', 0);
|
||||
return (
|
||||
<div
|
||||
className={styles.topContent}
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 600,
|
||||
color: cephStatusColorMap[data],
|
||||
height: 65,
|
||||
}}
|
||||
>
|
||||
{cephStatusMap[data]}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster Usage'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.cephStorageUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${used} / ${total}`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Disk allocation (GB)'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'monitorOverview.cephStorageAllocate',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const totalValue = parseFloat(get(data[1], 'y', 0).toFixed(2));
|
||||
const usedValue = parseFloat(
|
||||
(totalValue - get(data[0], 'y', 0)).toFixed(2)
|
||||
);
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 28, fontWeight: 600 }}>
|
||||
{computePercentage(usedValue, totalValue)}
|
||||
</span>
|
||||
%
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${usedValue} GB / ${totalValue} GB`}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const storageRightChartList = [
|
||||
{
|
||||
title: t('Storage Cluster IOPS'),
|
||||
createFetchParams: {
|
||||
requestType: 'range',
|
||||
metricKey: 'monitorOverview.cephStorageClusterIOPS',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: handleResponses,
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
span: 24,
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
height: 318,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
@ -15,13 +15,17 @@
|
||||
import React from 'react';
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import { Col, Row } from 'antd';
|
||||
import Charts from 'components/PrometheusChart/component/Charts';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import styles from './index.less';
|
||||
import AlertInfo from './components/AlertInfo';
|
||||
import ClusterCard from './components/ClusterMonitor/ClusterCard';
|
||||
import ClusterChart from './components/ClusterMonitor/ClusterChart';
|
||||
import TopCard from './components/Tops/TopCard';
|
||||
import StorageClusterCard from './components/StorageClusterMonitor/StorageClusterCard';
|
||||
import StorageClusterChart from './components/StorageClusterMonitor/StorageClusterChart';
|
||||
import {
|
||||
physicalNodeLeftTopCardList,
|
||||
physicalNodeRightTopCardList,
|
||||
storageLeftCardList,
|
||||
storageRightChartList,
|
||||
topCardList,
|
||||
} from './config';
|
||||
|
||||
const BaseContentConfig = {
|
||||
renderNodeSelect: false,
|
||||
@ -30,42 +34,51 @@ const BaseContentConfig = {
|
||||
|
||||
const Index = () => {
|
||||
return (
|
||||
<BaseContent
|
||||
{...BaseContentConfig}
|
||||
renderChartCards={(store) => (
|
||||
<Row gutter={[16, 16]} className={styles.container}>
|
||||
<Col span={24}>
|
||||
<AlertInfo />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<ClusterCard store={store} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<ClusterChart store={store} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TopCard store={store} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<StorageClusterCard store={store} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<StorageClusterChart
|
||||
store={store}
|
||||
BaseContentConfig={BaseContentConfig}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
/>
|
||||
<BaseContent {...BaseContentConfig}>
|
||||
<Row gutter={[16, 16]} className={styles.container}>
|
||||
<Col span={24}>
|
||||
<AlertInfo />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Charts topCardList={physicalNodeLeftTopCardList} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Charts topCardList={physicalNodeRightTopCardList} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Charts
|
||||
baseTopCardProps={{
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
requestType: 'current',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
visibleHeight: 200,
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>{store.data}</div>
|
||||
),
|
||||
}}
|
||||
topCardList={topCardList}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Charts topCardList={storageLeftCardList} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Charts chartCardList={storageRightChartList} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</BaseContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -72,6 +72,7 @@
|
||||
font-weight: 400;
|
||||
height: 100%;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,355 +0,0 @@
|
||||
// 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';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import { Col, Progress, Row } from 'antd';
|
||||
import { merge, get } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
import moment from 'moment';
|
||||
import { computePercentage, formatSize, formatUsedTime } from 'utils/index';
|
||||
import styles from './index.less';
|
||||
|
||||
@observer
|
||||
class Charts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = props.store;
|
||||
}
|
||||
|
||||
renderTopCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>{store.data}</div>
|
||||
),
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('CPU Cores'),
|
||||
span: 5,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.cpuCores',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>
|
||||
{get(store.data, 'length', 0)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Total Ram'),
|
||||
span: 5,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.totalMem',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>
|
||||
{getSuitableValue(get(store.data[0], 'y', 0), 'memory')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('System Running Time'),
|
||||
span: 5,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.systemRunningTime',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div className={styles.topContent}>
|
||||
{formatUsedTime(
|
||||
(moment().unix() -
|
||||
parseInt(get(store.data[0], 'y', moment().unix()), 10)) *
|
||||
1000
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('File System Free Space'),
|
||||
span: 9,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.fileSystemFreeSpace',
|
||||
formatDataFn: (...rest) => {
|
||||
const [data, typeKey, deviceKey] = rest;
|
||||
const [avail, size] = data;
|
||||
const { data: { result } = { result: [] } } = avail;
|
||||
const temp = [];
|
||||
result.forEach((item, index) => {
|
||||
temp.push({
|
||||
mountpoint:
|
||||
get(item, `metric.${deviceKey}`) +
|
||||
get(item, `metric.${typeKey}`),
|
||||
avail: parseFloat(get(item, 'value[1]', 0)),
|
||||
total: parseFloat(
|
||||
get(size, `data.result[${index}].value[1]`, 0)
|
||||
),
|
||||
});
|
||||
});
|
||||
return temp;
|
||||
},
|
||||
typeKey: 'mountpoint',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div
|
||||
style={{
|
||||
height: 100,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{(store.data || []).map((item, index) => {
|
||||
const percentage = computePercentage(item.avail, item.total);
|
||||
const percentageColor = percentage > 80 ? '#FAAD14' : '#1890FF';
|
||||
return (
|
||||
<div
|
||||
key={item.mountpoint}
|
||||
style={{ marginTop: index > 0 ? 16 : 0 }}
|
||||
>
|
||||
<div>
|
||||
<div style={{ float: 'left' }}>{item.mountpoint}</div>
|
||||
<div style={{ float: 'right' }}>
|
||||
{`${formatSize(parseInt(item.avail, 10))} / ${formatSize(
|
||||
parseInt(item.total, 10)
|
||||
)}`}
|
||||
</div>
|
||||
</div>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={(
|
||||
(parseInt(item.avail, 10) / parseInt(item.total, 10)) *
|
||||
100
|
||||
).toFixed(3)}
|
||||
strokeColor={percentageColor}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
renderChartCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 250,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('CPU Usage(%)'),
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.cpuUsage',
|
||||
typeKey: 'mode',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Memory Usage'),
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.memUsage',
|
||||
modifyKeys: [t('Used'), t('Free')],
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'memory', 0),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('DISK IOPS'),
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.diskIOPS',
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('DISK Usage(%)'),
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.diskUsage',
|
||||
typeKey: 'hostname',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('DISK Usage(%)'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINEDEVICES,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('System Load'),
|
||||
span: 24,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.systemLoad',
|
||||
typeKey: '__name__',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Traffic'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.networkTraffic',
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('TCP Connections'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.tcpConnections',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('TCP Connections'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Errors'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.networkErrors',
|
||||
typeKey: '__name__',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Network Errors'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Dropped Packets'),
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
metricKey: 'physicalNode.networkDroppedPackets',
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Network Dropped Packets'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<ChartCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
params={{
|
||||
instance: this.store.node.metric.instance,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.store.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>{this.renderTopCards()}</Col>
|
||||
<Col>{this.renderChartCards()}</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts;
|
@ -13,16 +13,274 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Progress } from 'antd';
|
||||
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import Charts from './Charts';
|
||||
import { getSuitableValue } from 'resources/monitoring';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import { computePercentage, formatSize, formatUsedTime } from 'src/utils';
|
||||
|
||||
const PhysicalNode = () => {
|
||||
function renderChartCards(store) {
|
||||
return <Charts store={store} />;
|
||||
}
|
||||
import styles from 'components/PrometheusChart/component/styles.less';
|
||||
|
||||
return <BaseContent renderChartCards={renderChartCards} />;
|
||||
export const topCardList = [
|
||||
{
|
||||
title: t('CPU Cores'),
|
||||
span: 5,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.cpuCores',
|
||||
},
|
||||
renderContent: (value) => (
|
||||
<div className={styles.topContent}>{get(value.data, 'length', 0)}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Total Ram'),
|
||||
span: 5,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.totalMem',
|
||||
},
|
||||
renderContent: (value) => (
|
||||
<div className={styles.topContent}>
|
||||
{getSuitableValue(get(value.data[0], 'y', 0), 'memory')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('System Running Time'),
|
||||
span: 5,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.systemRunningTime',
|
||||
},
|
||||
renderContent: (value) => (
|
||||
<div className={styles.topContent}>
|
||||
{formatUsedTime(
|
||||
(moment().unix() -
|
||||
parseInt(get(value.data[0], 'y', moment().unix()), 10)) *
|
||||
1000
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('File System Free Space'),
|
||||
span: 9,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.fileSystemFreeSpace',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (...rest) => {
|
||||
const [data, typeKey, deviceKey] = rest;
|
||||
const [avail, size] = data;
|
||||
const { data: { result } = { result: [] } } = avail;
|
||||
const temp = [];
|
||||
result.forEach((item, index) => {
|
||||
temp.push({
|
||||
mountpoint:
|
||||
get(item, `metric.${deviceKey}`) + get(item, `metric.${typeKey}`),
|
||||
avail: parseFloat(get(item, 'value[1]', 0)),
|
||||
total: parseFloat(get(size, `data.result[${index}].value[1]`, 0)),
|
||||
});
|
||||
});
|
||||
return temp;
|
||||
},
|
||||
typeKey: 'mountpoint',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
renderContent: (value) => (
|
||||
<div
|
||||
style={{
|
||||
height: 100,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{(value.data || []).map((item, index) => {
|
||||
const percentage = computePercentage(item.avail, item.total);
|
||||
const percentageColor = percentage > 80 ? '#FAAD14' : '#1890FF';
|
||||
return (
|
||||
<div
|
||||
key={item.mountpoint}
|
||||
style={{ marginTop: index > 0 ? 16 : 0 }}
|
||||
>
|
||||
<div>
|
||||
<div style={{ float: 'left' }}>{item.mountpoint}</div>
|
||||
<div style={{ float: 'right' }}>
|
||||
{`${formatSize(parseInt(item.avail, 10))} / ${formatSize(
|
||||
parseInt(item.total, 10)
|
||||
)}`}
|
||||
</div>
|
||||
</div>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={Number(
|
||||
(
|
||||
(parseInt(item.avail, 10) / parseInt(item.total, 10)) *
|
||||
100
|
||||
).toFixed(3)
|
||||
)}
|
||||
strokeColor={percentageColor}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const chartCardList = [
|
||||
{
|
||||
title: t('CPU Usage(%)'),
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.cpuUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: 'mode',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Memory Usage'),
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.memUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('Used'), t('Free')],
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'memory', 0),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('DISK IOPS'),
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.diskIOPS',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('DISK Usage(%)'),
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.diskUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: 'hostname',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('DISK Usage(%)'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINEDEVICES,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('System Load'),
|
||||
span: 24,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.systemLoad',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: '__name__',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Traffic'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.networkTraffic',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'traffic', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('TCP Connections'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.tcpConnections',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('TCP Connections'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Errors'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.networkErrors',
|
||||
},
|
||||
handleDataParams: {
|
||||
typeKey: '__name__',
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Network Errors'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.ONELINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Network Dropped Packets'),
|
||||
span: 12,
|
||||
createFetchParams: {
|
||||
metricKey: 'physicalNode.networkDroppedPackets',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('receive'), t('transmit')],
|
||||
deviceKey: 'device',
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
alias: t('Network Dropped Packets'),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINEDEVICES,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const chartConfig = {
|
||||
chartCardList,
|
||||
topCardList,
|
||||
};
|
||||
const PhysicalNode = () => <BaseContent chartConfig={chartConfig} />;
|
||||
|
||||
export default observer(PhysicalNode);
|
||||
export default PhysicalNode;
|
||||
|
@ -1,108 +0,0 @@
|
||||
.header {
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
|
||||
.range {
|
||||
:global {
|
||||
.ant-radio-button-wrapper {
|
||||
color: rgba(0, 0, 0, .65);
|
||||
}
|
||||
|
||||
.ant-radio-button-wrapper-checked {
|
||||
color: #0068FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download {
|
||||
float: right;
|
||||
|
||||
:global {
|
||||
.ant-btn-icon-only {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.myCardRow {
|
||||
.top {
|
||||
.content {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-card-bordered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.ant-card-body {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-card-bordered {
|
||||
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.09);
|
||||
|
||||
.ant-card-head {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
:global {
|
||||
.ant-tabs-tab {
|
||||
margin-right: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ant-tabs-nav::before {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,377 +0,0 @@
|
||||
// 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';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import ChartCard from 'components/PrometheusChart/ChartCard';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import { Col, Progress, Row } from 'antd';
|
||||
import { merge, get } from 'lodash';
|
||||
import BaseCard from 'components/PrometheusChart/BaseCard';
|
||||
import {
|
||||
cephStatusColorMap,
|
||||
cephStatusMap,
|
||||
getSuitableValue,
|
||||
} from 'resources/monitoring';
|
||||
import { computePercentage } from 'utils/index';
|
||||
import CircleChart from 'components/PrometheusChart/CircleWithRightLegend';
|
||||
import RenderTabs from './RenderTabs';
|
||||
import styles from './index.less';
|
||||
|
||||
@observer
|
||||
class Charts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = props.store;
|
||||
}
|
||||
|
||||
renderTopCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'current',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const data = get(store, 'data[0].y', 0);
|
||||
return <div className={styles.topContent}>{data}</div>;
|
||||
},
|
||||
visibleHeight: 120,
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Storage Cluster Status'),
|
||||
span: 6,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.cephHealthStatus',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const data = get(store.data, 'y', 0);
|
||||
return (
|
||||
<div
|
||||
className={styles.topContent}
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 600,
|
||||
color: cephStatusColorMap[data],
|
||||
}}
|
||||
>
|
||||
{cephStatusMap[data]}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Monitors',
|
||||
span: 9,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.cephMonitorStatus',
|
||||
formatDataFn: (...rest) => {
|
||||
const data = handleResponses(...rest);
|
||||
const status = [
|
||||
{
|
||||
type: 'down',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
type: 'up',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
data.forEach((i) => {
|
||||
status[i.y].value++;
|
||||
});
|
||||
return status;
|
||||
},
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div>
|
||||
<div style={{ height: 120 }}>
|
||||
<CircleChart data={store.data} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'PGs',
|
||||
span: 9,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.cephPGS',
|
||||
formatDataFn: (...rest) => {
|
||||
const data = handleResponses(...rest);
|
||||
return [
|
||||
{
|
||||
type: 'clean',
|
||||
value: get(data, '[0].y', 0),
|
||||
},
|
||||
{
|
||||
type: 'others',
|
||||
value: get(data, '[1].y', 0),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
renderContent: (store) => (
|
||||
<div>
|
||||
<div style={{ height: 120 }}>
|
||||
<CircleChart data={store.data} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'OSDs',
|
||||
span: 9,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.osdData',
|
||||
formatDataFn: (resps) => {
|
||||
const [inUp, inDown, outUp, outDown] = resps;
|
||||
return {
|
||||
inUp: getValue(inUp),
|
||||
inDown: getValue(inDown),
|
||||
outUp: getValue(outUp),
|
||||
outDown: getValue(outDown),
|
||||
};
|
||||
|
||||
function getValue(d) {
|
||||
return get(d, 'data.result[0].value[1]', 0);
|
||||
}
|
||||
},
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
return (
|
||||
<Row className={styles.OSDs}>
|
||||
<Col span={8} />
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Up')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Down')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('In Cluster')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.inUp}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.inDown}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Out Cluster')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.outUp}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.outDown}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Average PGs per OSD'),
|
||||
span: 5,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.avgPerOSD',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: t('Average OSD Apply Latency(ms)'),
|
||||
// span: 5,
|
||||
// constructorParams: {
|
||||
// metricKey: 'storageCluster.avgOSDApplyLatency',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: t('Average OSD Commit Latency(ms)'),
|
||||
// span: 5,
|
||||
// constructorParams: {
|
||||
// metricKey: 'storageCluster.avgOSDCommitLatency',
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: t('Storage Cluster Usage'),
|
||||
span: 10,
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.storageClusterUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
const progressPercentage = computePercentage(usedValue, totalValue);
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Row style={{ justifyContent: 'flex-end', height: '50%' }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginRight: 32,
|
||||
}}
|
||||
>
|
||||
{`${t('Used')} ${used} / ${t('Total')} ${total}`}
|
||||
</span>
|
||||
</Row>
|
||||
<Row style={{ height: '50%' }}>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={progressPercentage}
|
||||
strokeColor={
|
||||
progressPercentage > 80 ? '#FAAD14' : '#1890FF'
|
||||
}
|
||||
showInfo={progressPercentage !== 100}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<BaseCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
renderChartCards() {
|
||||
const baseConfig = {
|
||||
span: 12,
|
||||
constructorParams: {
|
||||
requestType: 'range',
|
||||
formatDataFn: handleResponses,
|
||||
},
|
||||
chartProps: {
|
||||
height: 250,
|
||||
scale: {
|
||||
y: {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartLists = [
|
||||
{
|
||||
title: t('Storage Pool Capacity Usage'),
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.poolCapacityUsage',
|
||||
modifyKeys: [t('used'), t('available')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'disk', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster OSD Latency'),
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.clusterOSDLatency',
|
||||
modifyKeys: ['apply', 'commit'],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster IOPS'),
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.clusterIOPS',
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster Bandwidth'),
|
||||
constructorParams: {
|
||||
metricKey: 'storageCluster.clusterBandwidth',
|
||||
modifyKeys: [t('in'), t('out')],
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'bandwidth', 0),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
{chartLists.map((chartProps) => {
|
||||
const config = merge({}, baseConfig, chartProps);
|
||||
const { span, ...rest } = config;
|
||||
return (
|
||||
<Col span={span} key={chartProps.constructorParams.metricKey}>
|
||||
<ChartCard
|
||||
{...rest}
|
||||
currentRange={this.store.currentRange}
|
||||
interval={this.store.interval}
|
||||
BaseContentConfig={this.props.BaseContentConfig}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.store.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Row gutter={[16, 16]} style={{ paddingTop: 16 }}>
|
||||
<Col span={24}>{this.renderTopCards()}</Col>
|
||||
<Col span={24}>{this.renderChartCards()}</Col>
|
||||
<Col span={24}>
|
||||
<RenderTabs store={this.store} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts;
|
@ -1,82 +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 { Spin, Tabs } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { formatSize } from 'utils/index';
|
||||
import FetchPrometheusStore from 'components/PrometheusChart/store/FetchPrometheusStore';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import metricDict from 'components/PrometheusChart/metricDict';
|
||||
import { fetchPrometheus } from 'components/PrometheusChart/utils/utils';
|
||||
import { Tabs } from 'antd';
|
||||
|
||||
import {
|
||||
createDataHandler,
|
||||
createFetchPrometheusClient,
|
||||
} from 'components/PrometheusChart/utils';
|
||||
import BaseTable from 'components/Tables/Base';
|
||||
import { fetchPrometheus } from 'components/PrometheusChart/utils/utils';
|
||||
import { formatSize } from 'src/utils';
|
||||
import List from 'stores/base-list';
|
||||
import styles from './index.less';
|
||||
import BaseContentContext from 'components/PrometheusChart/component/context';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
@observer
|
||||
class RenderTabs extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new FetchPrometheusStore({
|
||||
requestType: 'current',
|
||||
metricKey: 'storageCluster.tabs',
|
||||
modifyKeys: ['pools', 'osds'],
|
||||
formatDataFn: this.formatDataFn,
|
||||
});
|
||||
this.state = {
|
||||
filters: {},
|
||||
};
|
||||
}
|
||||
const RenderTabs = () => {
|
||||
const [filters, setFilters] = useState({});
|
||||
const [initData, setInitData] = useState([]);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [tab, setTab] = useState('pool');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
componentDidMount() {
|
||||
this.getData();
|
||||
}
|
||||
const ctx = useContext(BaseContentContext);
|
||||
const fetchData = createFetchPrometheusClient({
|
||||
requestType: 'current',
|
||||
metricKey: 'storageCluster.tabs',
|
||||
});
|
||||
|
||||
async getData() {
|
||||
await this.store
|
||||
.fetchData({
|
||||
currentRange: this.props.store.currentRange,
|
||||
interval: this.props.store.interval,
|
||||
})
|
||||
.then(() => {
|
||||
this.getListData();
|
||||
const dataHandler = createDataHandler({
|
||||
modifyKeys: ['pools', 'osds'],
|
||||
formatDataFn: (resps) => {
|
||||
const retData = [];
|
||||
const [pool, osd] = resps;
|
||||
get(pool, 'data.result', []).forEach((r) => {
|
||||
const { metric, value } = r;
|
||||
retData.push({
|
||||
type: 'pool',
|
||||
...metric,
|
||||
value: parseFloat(value[1]) || 0,
|
||||
});
|
||||
});
|
||||
get(osd, 'data.result', []).forEach((r) => {
|
||||
const { metric, value } = r;
|
||||
retData.push({
|
||||
type: 'osd',
|
||||
...metric,
|
||||
value: parseFloat(value[1]) || 0,
|
||||
});
|
||||
});
|
||||
return retData;
|
||||
},
|
||||
});
|
||||
|
||||
function getListData(data) {
|
||||
let originData = data.filter((d) => d.type === tab);
|
||||
Object.keys(filters).forEach((key) => {
|
||||
originData = originData.filter((i) => i[key] === filters[key]);
|
||||
});
|
||||
setListData(originData);
|
||||
}
|
||||
|
||||
async getListData() {
|
||||
const { data } = this.store;
|
||||
async function handleInitData(data) {
|
||||
const newData = [...data];
|
||||
const poolPromises = get(metricDict, 'storageCluster.poolTab.url', []).map(
|
||||
const poolPromises = get(METRICDICT, 'storageCluster.poolTab.url', []).map(
|
||||
(item) => fetchPrometheus(item, 'current')
|
||||
);
|
||||
const osdPromises = get(metricDict, 'storageCluster.osdTab.url', []).map(
|
||||
const osdPromises = get(METRICDICT, 'storageCluster.osdTab.url', []).map(
|
||||
(item) => fetchPrometheus(item, 'current')
|
||||
);
|
||||
|
||||
const poolRets = await Promise.all(poolPromises);
|
||||
poolRets.forEach((ret, index) => {
|
||||
handler(ret, index, 'pool_id');
|
||||
});
|
||||
|
||||
const osdRets = await Promise.all(osdPromises);
|
||||
osdRets.forEach((ret, index) => {
|
||||
handler(ret, index, 'ceph_daemon');
|
||||
});
|
||||
this.store.updateData(newData);
|
||||
|
||||
function handler(ret, index, primaryKey) {
|
||||
ret.data.result.forEach((item) => {
|
||||
const { metric, value } = item;
|
||||
@ -107,106 +99,96 @@ class RenderTabs extends Component {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const poolRets = await Promise.all(poolPromises);
|
||||
poolRets.forEach((ret, index) => {
|
||||
handler(ret, index, 'pool_id');
|
||||
});
|
||||
|
||||
const osdRets = await Promise.all(osdPromises);
|
||||
osdRets.forEach((ret, index) => {
|
||||
handler(ret, index, 'ceph_daemon');
|
||||
});
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
get tableData() {
|
||||
const { filters } = this.state;
|
||||
let originData = this.store.data.filter(
|
||||
(d) => d.type === (this.store.device || this.store.data[0].type)
|
||||
);
|
||||
Object.keys(filters).forEach((key) => {
|
||||
originData = originData.filter((i) => i[key] === filters[key]);
|
||||
async function getData() {
|
||||
setIsLoading(true);
|
||||
const d = await fetchData({
|
||||
currentRange: ctx.range,
|
||||
interval: ctx.interval,
|
||||
});
|
||||
return originData;
|
||||
const { retData } = dataHandler(d);
|
||||
const newInitData = await handleInitData(retData);
|
||||
setInitData(newInitData);
|
||||
getListData(newInitData);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
formatDataFn(resps) {
|
||||
const retData = [];
|
||||
const [pool, osd] = resps;
|
||||
get(pool, 'data.result', []).forEach((r) => {
|
||||
const { metric, value } = r;
|
||||
retData.push({
|
||||
type: 'pool',
|
||||
...metric,
|
||||
value: parseFloat(value[1]) || 0,
|
||||
});
|
||||
});
|
||||
get(osd, 'data.result', []).forEach((r) => {
|
||||
const { metric, value } = r;
|
||||
retData.push({
|
||||
type: 'osd',
|
||||
...metric,
|
||||
value: parseFloat(value[1]) || 0,
|
||||
});
|
||||
});
|
||||
return retData;
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
render() {
|
||||
const { device = 'pool' } = this.store;
|
||||
const columns = device === 'pool' ? poolsColumns : osdsColumns;
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
defaultActiveKey="pool"
|
||||
onChange={(e) => {
|
||||
this.setState(
|
||||
{
|
||||
filters: {},
|
||||
},
|
||||
() => {
|
||||
this.store.handleDeviceChange(e);
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<TabPane tab="Pools" key="pool" />
|
||||
<TabPane tab="OSDs" key="osd" />
|
||||
</Tabs>
|
||||
{this.store.isLoading ? (
|
||||
<div className={styles.spinContainer}>
|
||||
<Spin />
|
||||
</div>
|
||||
) : (
|
||||
<BaseTable
|
||||
resourceName={this.store.device === 'pool' ? t('Pools') : t('OSDs')}
|
||||
rowKey={this.store.device === 'pool' ? 'pool_id' : 'name'}
|
||||
columns={columns}
|
||||
data={this.tableData}
|
||||
pagination={{
|
||||
...new List(),
|
||||
total: this.tableData.length,
|
||||
}}
|
||||
hideRefresh
|
||||
searchFilters={
|
||||
this.store.device === 'pool'
|
||||
? [
|
||||
{
|
||||
label: t('Pool Name'),
|
||||
name: 'name',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: t('Name'),
|
||||
name: 'ceph_daemon',
|
||||
},
|
||||
]
|
||||
}
|
||||
itemActions={[]}
|
||||
onFilterChange={(filters) => {
|
||||
const { limit, page, sortKey, sortOrder, ...rest } = filters;
|
||||
this.setState({
|
||||
filters: rest,
|
||||
});
|
||||
}}
|
||||
onFetchBySort={() => {}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
getListData(initData);
|
||||
}, [tab, filters]);
|
||||
|
||||
const columns = tab === 'pool' ? poolsColumns : osdsColumns;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
defaultActiveKey="pool"
|
||||
onChange={(e) => {
|
||||
setFilters({});
|
||||
setTab(e);
|
||||
}}
|
||||
>
|
||||
<TabPane tab="Pools" key="pool" />
|
||||
<TabPane tab="OSDs" key="osd" />
|
||||
</Tabs>
|
||||
{/* {isLoading ? (
|
||||
<div className={styles.spinContainer}>
|
||||
<Spin />
|
||||
</div>
|
||||
) : ( */}
|
||||
<BaseTable
|
||||
isLoading={isLoading}
|
||||
resourceName={tab === 'pool' ? t('Pools') : t('OSDs')}
|
||||
rowKey={tab === 'pool' ? 'pool_id' : 'name'}
|
||||
columns={columns}
|
||||
data={listData}
|
||||
pagination={{
|
||||
...new List(),
|
||||
total: listData.length,
|
||||
}}
|
||||
hideRefresh
|
||||
searchFilters={
|
||||
tab === 'pool'
|
||||
? [
|
||||
{
|
||||
label: t('Pool Name'),
|
||||
name: 'name',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: t('Name'),
|
||||
name: 'ceph_daemon',
|
||||
},
|
||||
]
|
||||
}
|
||||
itemActions={[]}
|
||||
onFilterChange={(newFilters) => {
|
||||
const { limit, page, sortKey, sortOrder, ...rest } = newFilters;
|
||||
setFilters(rest);
|
||||
}}
|
||||
/>
|
||||
{/* )} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderTabs;
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
// 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 { observer } from 'mobx-react';
|
||||
import Base from 'containers/List';
|
||||
|
||||
@observer
|
||||
export default class TabTable extends Base {
|
||||
componentDidMount() {}
|
||||
|
||||
getData() {}
|
||||
|
||||
getDataSource = () => {
|
||||
return this.list.data;
|
||||
};
|
||||
|
||||
get params() {
|
||||
return {};
|
||||
}
|
||||
|
||||
get list() {
|
||||
return {
|
||||
data: this.props.store.data.filter(
|
||||
(d) => d.type === (this.props.store.device || 'pool')
|
||||
),
|
||||
filters: [],
|
||||
};
|
||||
}
|
||||
|
||||
get location() {
|
||||
return {
|
||||
search: [],
|
||||
};
|
||||
}
|
||||
|
||||
get rowKey() {
|
||||
const { tabKey } = this.store;
|
||||
return tabKey === 'pools' ? 'pool_id' : 'ceph_daemon';
|
||||
}
|
||||
|
||||
get name() {
|
||||
return t('tab tables');
|
||||
}
|
||||
|
||||
get actionConfigs() {
|
||||
return {
|
||||
rowActions: {},
|
||||
primaryActions: [],
|
||||
};
|
||||
}
|
||||
|
||||
getColumns = () => {
|
||||
const { columns } = this.props;
|
||||
return columns || [];
|
||||
};
|
||||
|
||||
get searchFilters() {
|
||||
const { searchFilters } = this.props;
|
||||
return searchFilters || [];
|
||||
}
|
||||
}
|
@ -13,22 +13,284 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { get } from 'lodash';
|
||||
import { Col, Progress, Row } from 'antd';
|
||||
|
||||
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||
import Charts from './Charts';
|
||||
import {
|
||||
cephStatusColorMap,
|
||||
cephStatusMap,
|
||||
getSuitableValue,
|
||||
} from 'resources/monitoring';
|
||||
import CircleChart from 'components/PrometheusChart/CircleWithRightLegend';
|
||||
import { handleResponses } from 'components/PrometheusChart/utils/dataHandler';
|
||||
import { computePercentage } from 'src/utils';
|
||||
import { ChartType } from 'components/PrometheusChart/utils/utils';
|
||||
import RenderTabs from './RenderTabs';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const StorageCluster = () => {
|
||||
const BaseContentConfig = {
|
||||
renderNodeSelect: false,
|
||||
const topCardList = [
|
||||
{
|
||||
title: t('Storage Cluster Status'),
|
||||
span: 6,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.cephHealthStatus',
|
||||
},
|
||||
renderContent: ({ data }) => {
|
||||
const d = get(data, 'y', 0);
|
||||
return (
|
||||
<div
|
||||
className={styles.topContent}
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 600,
|
||||
color: cephStatusColorMap[d],
|
||||
}}
|
||||
>
|
||||
{cephStatusMap[d]}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Monitors',
|
||||
span: 9,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.cephMonitorStatus',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (...rest) => {
|
||||
const data = handleResponses(...rest);
|
||||
const status = [
|
||||
{
|
||||
type: 'down',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
type: 'up',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
data.forEach((i) => {
|
||||
const newVal = status[i.y].value + 1;
|
||||
status[i.y].value = newVal;
|
||||
});
|
||||
return status;
|
||||
},
|
||||
},
|
||||
renderContent: ({ data }) => (
|
||||
<div>
|
||||
<div style={{ height: 120 }}>
|
||||
<CircleChart data={data} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'PGs',
|
||||
span: 9,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.cephPGS',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (...rest) => {
|
||||
const data = handleResponses(...rest);
|
||||
return [
|
||||
{
|
||||
type: 'clean',
|
||||
value: get(data, '[0].y', 0),
|
||||
},
|
||||
{
|
||||
type: 'others',
|
||||
value: get(data, '[1].y', 0),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
renderContent: ({ data }) => (
|
||||
<div>
|
||||
<div style={{ height: 120 }}>
|
||||
<CircleChart data={data} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'OSDs',
|
||||
span: 9,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.osdData',
|
||||
},
|
||||
handleDataParams: {
|
||||
formatDataFn: (resps) => {
|
||||
function getValue(d) {
|
||||
return get(d, 'data.result[0].value[1]', 0);
|
||||
}
|
||||
const [inUp, inDown, outUp, outDown] = resps;
|
||||
return {
|
||||
inUp: getValue(inUp),
|
||||
inDown: getValue(inDown),
|
||||
outUp: getValue(outUp),
|
||||
outDown: getValue(outDown),
|
||||
};
|
||||
},
|
||||
},
|
||||
renderContent: ({ data }) => {
|
||||
return (
|
||||
<Row className={styles.OSDs}>
|
||||
<Col span={8} />
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Up')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Down')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('In Cluster')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.inUp}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.inDown}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 14, opacity: 0.8 }}>
|
||||
{t('Out Cluster')}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.outUp}
|
||||
</Col>
|
||||
<Col span={8} style={{ fontSize: 18 }}>
|
||||
{data.outDown}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Average PGs per OSD'),
|
||||
span: 5,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.avgPerOSD',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster Usage'),
|
||||
span: 10,
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.storageClusterUsage',
|
||||
},
|
||||
renderContent: (store) => {
|
||||
const { data } = store;
|
||||
const usedValue = get(data[0], 'y', 0);
|
||||
const totalValue = get(data[1], 'y', 0);
|
||||
const used = getSuitableValue(usedValue, 'disk');
|
||||
const total = getSuitableValue(totalValue, 'disk');
|
||||
const progressPercentage = computePercentage(usedValue, totalValue);
|
||||
return (
|
||||
<div className={styles.topContent}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Row style={{ justifyContent: 'flex-end', height: '50%' }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginRight: 32,
|
||||
}}
|
||||
>
|
||||
{`${t('Used')} ${used} / ${t('Total')} ${total}`}
|
||||
</span>
|
||||
</Row>
|
||||
<Row style={{ height: '50%' }}>
|
||||
<Progress
|
||||
style={{ width: '95%' }}
|
||||
percent={progressPercentage}
|
||||
strokeColor={progressPercentage > 80 ? '#FAAD14' : '#1890FF'}
|
||||
showInfo={progressPercentage !== 100}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const chartCardList = [
|
||||
{
|
||||
title: t('Storage Pool Capacity Usage'),
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.poolCapacityUsage',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('used'), t('available')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'disk', 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster OSD Latency'),
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.clusterOSDLatency',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: ['apply', 'commit'],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster IOPS'),
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.clusterIOPS',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('read'), t('write')],
|
||||
},
|
||||
chartProps: {
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Storage Cluster Bandwidth'),
|
||||
createFetchParams: {
|
||||
metricKey: 'storageCluster.clusterBandwidth',
|
||||
},
|
||||
handleDataParams: {
|
||||
modifyKeys: [t('in'), t('out')],
|
||||
},
|
||||
chartProps: {
|
||||
scale: {
|
||||
y: {
|
||||
formatter: (d) => getSuitableValue(d, 'bandwidth', 0),
|
||||
},
|
||||
},
|
||||
chartType: ChartType.MULTILINE,
|
||||
},
|
||||
},
|
||||
];
|
||||
const chartConfig = {
|
||||
chartCardList,
|
||||
topCardList,
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseContent renderChartCards={renderChartCards} {...BaseContentConfig} />
|
||||
<BaseContent renderNodeSelect={false} chartConfig={chartConfig}>
|
||||
<RenderTabs />
|
||||
</BaseContent>
|
||||
);
|
||||
|
||||
function renderChartCards(store) {
|
||||
return <Charts store={store} BaseContentConfig={BaseContentConfig} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default observer(StorageCluster);
|
||||
export default StorageCluster;
|
||||
|
@ -27,7 +27,7 @@ export default class MonitorBase extends Base {
|
||||
}
|
||||
|
||||
@observable
|
||||
currentRange = defaultOneHourAgo;
|
||||
currentRange = defaultOneHourAgo();
|
||||
|
||||
@observable
|
||||
interval = 10;
|
||||
|
@ -80,9 +80,7 @@ export class OpenstackServiceStore extends MonitorBase {
|
||||
const tmp = [];
|
||||
try {
|
||||
const [currentState, last24State, libvirtdState, libvirtd24State] =
|
||||
await Promise.all(
|
||||
getPromises.call(this, 'openstackService.novaService')
|
||||
);
|
||||
await Promise.all(getPromises('openstackService.novaService'));
|
||||
const {
|
||||
data: { result: currentStateResult },
|
||||
} = currentState;
|
||||
|
Loading…
Reference in New Issue
Block a user