feature: Add monitor overview
add monitor overview Change-Id: I1e66ffde80ae2c162c1a68411cdd2cfa98ec8931
This commit is contained in:
parent
986891f430
commit
a210d80f5c
@ -18,7 +18,7 @@ import {
|
|||||||
DatabaseOutlined,
|
DatabaseOutlined,
|
||||||
CreditCardOutlined,
|
CreditCardOutlined,
|
||||||
GlobalOutlined,
|
GlobalOutlined,
|
||||||
// ToolOutlined,
|
MonitorOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
AppstoreOutlined,
|
AppstoreOutlined,
|
||||||
@ -587,6 +587,22 @@ const renderMenu = (t) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/monitor-center',
|
||||||
|
name: t('Monitor Center'),
|
||||||
|
key: '/monitorCenterAdmin',
|
||||||
|
icon: <MonitorOutlined />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/monitor-center/overview-admin',
|
||||||
|
name: t('Monitor Overview'),
|
||||||
|
key: 'monitorOverviewAdmin',
|
||||||
|
level: 1,
|
||||||
|
children: [],
|
||||||
|
hasBreadcrumb: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/configuration-admin',
|
path: '/configuration-admin',
|
||||||
name: t('Global Setting'),
|
name: t('Global Setting'),
|
||||||
|
@ -41,6 +41,9 @@ const Heat = lazy(() =>
|
|||||||
const UserCenter = lazy(() =>
|
const UserCenter = lazy(() =>
|
||||||
import(/* webpackChunkName: "user-center" */ 'pages/user-center/App')
|
import(/* webpackChunkName: "user-center" */ 'pages/user-center/App')
|
||||||
);
|
);
|
||||||
|
const MonitorCenter = lazy(() =>
|
||||||
|
import(/* webpackChunkName: "monitor-center" */ 'pages/monitor/App')
|
||||||
|
);
|
||||||
const E404 = lazy(() =>
|
const E404 = lazy(() =>
|
||||||
import(/* webpackChunkName: "E404" */ 'pages/base/containers/404')
|
import(/* webpackChunkName: "E404" */ 'pages/base/containers/404')
|
||||||
);
|
);
|
||||||
@ -77,6 +80,10 @@ export default [
|
|||||||
path: `/heat`,
|
path: `/heat`,
|
||||||
component: Heat,
|
component: Heat,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `/monitor-center`,
|
||||||
|
component: MonitorCenter,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `/user`,
|
path: `/user`,
|
||||||
component: UserCenter,
|
component: UserCenter,
|
||||||
|
21
src/pages/monitor/App.jsx
Normal file
21
src/pages/monitor/App.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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 renderRoutes from 'utils/RouterConfig';
|
||||||
|
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
const App = (props) => renderRoutes(routes, props);
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,55 @@
|
|||||||
|
// 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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
// 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, { useState, useEffect } from 'react';
|
||||||
|
import { Col, 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';
|
||||||
|
|
||||||
|
const STEP = 15;
|
||||||
|
|
||||||
|
const Index = function () {
|
||||||
|
const store = new FetchPrometheusStore({
|
||||||
|
requestType: 'range',
|
||||||
|
metricKey: 'monitorOverview.alertInfo',
|
||||||
|
formatDataFn: (responses, typeKey, deviceKey, modifyKeys) => {
|
||||||
|
const ret = [];
|
||||||
|
responses.forEach((response, idx) => {
|
||||||
|
ret.push(handleResponse(response, typeKey, deviceKey, modifyKeys[idx]));
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
modifyKeys: ['cpu', 'memory'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [cpuCount, setCpuCount] = useState(0);
|
||||||
|
const [memCount, setMemCount] = useState(0);
|
||||||
|
const [weekData, setWeekData] = useState(build7DaysData());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const end = moment();
|
||||||
|
const start = moment().startOf('day');
|
||||||
|
setIsLoading(true);
|
||||||
|
store
|
||||||
|
.fetchData({
|
||||||
|
interval: STEP,
|
||||||
|
currentRange: [start, end],
|
||||||
|
})
|
||||||
|
.then((d) => {
|
||||||
|
const [cpuData, memoryData] = d;
|
||||||
|
const newCpuCount = cpuData.reduce(
|
||||||
|
(pre, cur, idx) =>
|
||||||
|
idx > 0 && cur.x - cpuData[idx - 1].x > STEP ? pre + 1 : pre,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const newMemoryCount = memoryData.reduce(
|
||||||
|
(pre, cur, idx) =>
|
||||||
|
idx > 0 && cur.x - memoryData[idx - 1].x > STEP ? pre + 1 : pre,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
setCpuCount(newCpuCount);
|
||||||
|
setMemCount(newMemoryCount);
|
||||||
|
weekData[6].count = newCpuCount + newMemoryCount;
|
||||||
|
setWeekData([...weekData]);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isLoading ? (
|
||||||
|
<Spin />
|
||||||
|
) : (
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col flex="1 1">
|
||||||
|
<div className={styles.card}>
|
||||||
|
<Row style={{ height: '100%' }}>
|
||||||
|
<Col span={12} className={styles.alertCardLine}>
|
||||||
|
<div className={styles.number}>{cpuCount}</div>
|
||||||
|
<div>{t('Today CPU usage > 80% alert')}</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12} className={styles.alertCardLine}>
|
||||||
|
<div className={styles.number}>{memCount}</div>
|
||||||
|
<div>{t('Today Memory usage > 80% alert')}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col flex="0 1 440px">
|
||||||
|
<AlertChart data={weekData} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function build7DaysData() {
|
||||||
|
const today = moment().startOf('day');
|
||||||
|
const ret = [];
|
||||||
|
for (let index = 6; index >= 0; index--) {
|
||||||
|
ret.push({
|
||||||
|
date: today.clone().subtract(index, 'day').format('MM-DD'),
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Index;
|
@ -0,0 +1,171 @@
|
|||||||
|
// 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;
|
@ -0,0 +1,68 @@
|
|||||||
|
// 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 styles from 'pages/monitor/containers/Overview/index.less';
|
||||||
|
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.services_state === 'up' ? 0 : 1;
|
||||||
|
status[idx].value += parseInt(sta.value[1], 10);
|
||||||
|
});
|
||||||
|
return status;
|
||||||
|
},
|
||||||
|
metricKey: 'monitorOverview.computeNodeStatus',
|
||||||
|
},
|
||||||
|
renderContent: (store) => (
|
||||||
|
<div className={styles.topContent}>
|
||||||
|
<div style={{ height: 218 }}>
|
||||||
|
<CircleChart data={store.data} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
visibleHeight: 230,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseCard
|
||||||
|
{...config}
|
||||||
|
currentRange={propStore.currentRange}
|
||||||
|
interval={propStore.interval}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ClusterChart;
|
@ -0,0 +1,155 @@
|
|||||||
|
// 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;
|
@ -0,0 +1,57 @@
|
|||||||
|
// 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;
|
@ -0,0 +1,187 @@
|
|||||||
|
// 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;
|
72
src/pages/monitor/containers/Overview/index.jsx
Normal file
72
src/pages/monitor/containers/Overview/index.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 React from 'react';
|
||||||
|
import BaseContent from 'components/PrometheusChart/component/BaseContent';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const BaseContentConfig = {
|
||||||
|
renderNodeSelect: false,
|
||||||
|
renderTimeRangeSelect: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
126
src/pages/monitor/containers/Overview/index.less
Normal file
126
src/pages/monitor/containers/Overview/index.less
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
.container {
|
||||||
|
.card {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.05),
|
||||||
|
0px 0px 10px 0px rgba(0, 0, 0, 0.05);
|
||||||
|
color: #000000;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
:global {
|
||||||
|
.ant-tabs-tab {
|
||||||
|
margin-right: 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav::before {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertCardLine {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-size: 36px;
|
||||||
|
color: rgb(232, 104, 74);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertCardTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertInfo {
|
||||||
|
margin-top: 6px;
|
||||||
|
|
||||||
|
.alertNumber {
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertLevel {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertCardBottom {
|
||||||
|
padding: 0 26px;
|
||||||
|
|
||||||
|
.buttonTitle {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertLineInfo {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 100%;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.outer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexColumnAround {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/pages/monitor/routes/index.js
Normal file
29
src/pages/monitor/routes/index.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 BaseLayout from 'layouts/Basic';
|
||||||
|
import E404 from 'pages/base/containers/404';
|
||||||
|
import Overview from '../containers/Overview';
|
||||||
|
|
||||||
|
const PATH = '/monitor-center';
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: PATH,
|
||||||
|
component: BaseLayout,
|
||||||
|
routes: [
|
||||||
|
{ path: `${PATH}/overview-admin`, component: Overview, exact: true },
|
||||||
|
{ path: '*', component: E404 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
Loading…
Reference in New Issue
Block a user