Merge "feature: Add physical node monitor"

This commit is contained in:
Zuul 2021-12-01 16:45:16 +00:00 committed by Gerrit Code Review
commit c8319fe03b
5 changed files with 505 additions and 0 deletions

View File

@ -601,6 +601,14 @@ const renderMenu = (t) => {
children: [],
hasBreadcrumb: true,
},
{
path: '/monitor-center/physical-node-admin',
name: t('Physical Node'),
key: 'monitorPhysicalNodeAdmin',
level: 1,
children: [],
hasBreadcrumb: true,
},
],
},
{

View File

@ -0,0 +1,355 @@
// 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

@ -0,0 +1,28 @@
// 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 PhysicalNode = () => {
function renderChartCards(store) {
return <Charts store={store} />;
}
return <BaseContent renderChartCards={renderChartCards} />;
};
export default observer(PhysicalNode);

View File

@ -0,0 +1,108 @@
.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

@ -14,6 +14,7 @@
import BaseLayout from 'layouts/Basic';
import E404 from 'pages/base/containers/404';
import PhysicalNode from '../containers/PhysicalNode';
import Overview from '../containers/Overview';
const PATH = '/monitor-center';
@ -23,6 +24,11 @@ export default [
component: BaseLayout,
routes: [
{ path: `${PATH}/overview-admin`, component: Overview, exact: true },
{
path: `${PATH}/physical-node-admin`,
component: PhysicalNode,
exact: true,
},
{ path: '*', component: E404 },
],
},