refact: Refact Monitor page

refact monitor page

Change-Id: Ic7ff74641da63c236e27a146b6f1ab712a780f49
This commit is contained in:
zhuyue 2022-01-09 13:28:53 +08:00
parent 6872acf19b
commit 656ccfc9f6
51 changed files with 2493 additions and 3362 deletions

View File

@ -74,6 +74,7 @@
"globals": {
"t": true,
"globals": true,
"request": true
"request": true,
"METRICDICT": true
}
}

View File

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

View File

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

View File

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

View 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;

View 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;

View File

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

View File

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

View 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;

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
.topContent {
font-size: 24px;
font-weight: 500;
height: 120px;
display: flex;
justify-content: center;
align-items: center;
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,74 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React 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>
);
};

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

View File

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

View File

@ -72,6 +72,7 @@
font-weight: 400;
height: 100%;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 16px;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ export default class MonitorBase extends Base {
}
@observable
currentRange = defaultOneHourAgo;
currentRange = defaultOneHourAgo();
@observable
interval = 10;

View File

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