Merge "feature: Add storage cluster monitor page"
This commit is contained in:
commit
c8f9e19e63
@ -609,6 +609,14 @@ const renderMenu = (t) => {
|
|||||||
children: [],
|
children: [],
|
||||||
hasBreadcrumb: true,
|
hasBreadcrumb: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/monitor-center/storage-cluster-admin',
|
||||||
|
name: t('Storage Cluster'),
|
||||||
|
key: 'monitorStorageClusterAdmin',
|
||||||
|
level: 1,
|
||||||
|
children: [],
|
||||||
|
hasBreadcrumb: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
377
src/pages/monitor/containers/StorageCluster/Charts.jsx
Normal file
377
src/pages/monitor/containers/StorageCluster/Charts.jsx
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
// 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;
|
285
src/pages/monitor/containers/StorageCluster/RenderTabs.jsx
Normal file
285
src/pages/monitor/containers/StorageCluster/RenderTabs.jsx
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
// 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 { get } from 'lodash';
|
||||||
|
import metricDict from 'components/PrometheusChart/metricDict';
|
||||||
|
import { fetchPrometheus } from 'components/PrometheusChart/utils/utils';
|
||||||
|
import BaseTable from 'components/Tables/Base';
|
||||||
|
import List from 'stores/base-list';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
await this.store
|
||||||
|
.fetchData({
|
||||||
|
currentRange: this.props.store.currentRange,
|
||||||
|
interval: this.props.store.interval,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.getListData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getListData() {
|
||||||
|
const { data } = this.store;
|
||||||
|
const newData = [...data];
|
||||||
|
const poolPromises = get(metricDict, 'storageCluster.poolTab.url', []).map(
|
||||||
|
(item) => fetchPrometheus(item, 'current')
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
const itemIndex = newData.findIndex(
|
||||||
|
(p) => p[primaryKey] === metric[primaryKey]
|
||||||
|
);
|
||||||
|
// 特殊处理计算表达式没有metric.__name__的情况,
|
||||||
|
// 此处usage是details的第3个查询表达式,所以判断index===3, 两个Tab放一起处理了,都放在第3个。
|
||||||
|
// console.log(metric.__name__);
|
||||||
|
if (index === 3) {
|
||||||
|
newData[itemIndex].usage = parseFloat(
|
||||||
|
parseFloat(value[1]).toFixed(2)
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
[
|
||||||
|
'ceph_pool_objects',
|
||||||
|
'ceph_pg_total',
|
||||||
|
'ceph_pool_max_avail',
|
||||||
|
'ceph_osd_weight',
|
||||||
|
'ceph_osd_apply_latency_ms',
|
||||||
|
'ceph_osd_commit_latency_ms',
|
||||||
|
'ceph_osd_stat_bytes',
|
||||||
|
].indexOf(metric.__name__) > -1
|
||||||
|
) {
|
||||||
|
newData[itemIndex][metric.__name__] = parseInt(value[1], 10);
|
||||||
|
} else {
|
||||||
|
newData[itemIndex][metric.__name__] = value[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
return originData;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderTabs;
|
||||||
|
|
||||||
|
const poolsColumns = [
|
||||||
|
{
|
||||||
|
title: t('Pool Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('PGs'),
|
||||||
|
dataIndex: 'ceph_pg_total',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Objects'),
|
||||||
|
dataIndex: 'ceph_pool_objects',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Max Avail'),
|
||||||
|
dataIndex: 'ceph_pool_max_avail',
|
||||||
|
render: (text) => formatSize(text),
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Usage'),
|
||||||
|
dataIndex: 'usage',
|
||||||
|
render: (text) => `${text}%`,
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const osdsColumns = [
|
||||||
|
{
|
||||||
|
title: t('Name'),
|
||||||
|
dataIndex: 'ceph_daemon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'ceph_osd_up',
|
||||||
|
render: (up) => (up === '1' ? t('Up') : t('Down')),
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Instance Addr'),
|
||||||
|
dataIndex: 'cluster_addr',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Weight'),
|
||||||
|
dataIndex: 'ceph_osd_weight',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Apply Latency(ms)'),
|
||||||
|
dataIndex: 'ceph_osd_apply_latency_ms',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Commit Latency(ms)'),
|
||||||
|
dataIndex: 'ceph_osd_commit_latency_ms',
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Total Capacity'),
|
||||||
|
dataIndex: 'ceph_osd_stat_bytes',
|
||||||
|
render: (text) => formatSize(text),
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Usage'),
|
||||||
|
dataIndex: 'usage',
|
||||||
|
render: (text) => `${parseFloat(text).toFixed(2)}%`,
|
||||||
|
isHideable: true,
|
||||||
|
},
|
||||||
|
];
|
72
src/pages/monitor/containers/StorageCluster/TabTable.jsx
Normal file
72
src/pages/monitor/containers/StorageCluster/TabTable.jsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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 || [];
|
||||||
|
}
|
||||||
|
}
|
34
src/pages/monitor/containers/StorageCluster/index.jsx
Normal file
34
src/pages/monitor/containers/StorageCluster/index.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||||
|
import Charts from './Charts';
|
||||||
|
|
||||||
|
const StorageCluster = () => {
|
||||||
|
const BaseContentConfig = {
|
||||||
|
renderNodeSelect: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseContent renderChartCards={renderChartCards} {...BaseContentConfig} />
|
||||||
|
);
|
||||||
|
|
||||||
|
function renderChartCards(store) {
|
||||||
|
return <Charts store={store} BaseContentConfig={BaseContentConfig} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(StorageCluster);
|
119
src/pages/monitor/containers/StorageCluster/index.less
Normal file
119
src/pages/monitor/containers/StorageCluster/index.less
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
.OSDs {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
overflow: auto;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 120px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinContainer {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
padding: 30px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
import BaseLayout from 'layouts/Basic';
|
import BaseLayout from 'layouts/Basic';
|
||||||
import E404 from 'pages/base/containers/404';
|
import E404 from 'pages/base/containers/404';
|
||||||
import PhysicalNode from '../containers/PhysicalNode';
|
import PhysicalNode from '../containers/PhysicalNode';
|
||||||
|
import StorageCluster from '../containers/StorageCluster';
|
||||||
import Overview from '../containers/Overview';
|
import Overview from '../containers/Overview';
|
||||||
|
|
||||||
const PATH = '/monitor-center';
|
const PATH = '/monitor-center';
|
||||||
@ -29,6 +30,11 @@ export default [
|
|||||||
component: PhysicalNode,
|
component: PhysicalNode,
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/storage-cluster-admin`,
|
||||||
|
component: StorageCluster,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{ path: '*', component: E404 },
|
{ path: '*', component: E404 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user