feat: 运维管理 仪表盘

This commit is contained in:
jackhoo_98 2023-02-27 18:10:06 +08:00
parent fd0c3f2eb8
commit 401634be3e
8 changed files with 1206 additions and 0 deletions

12
src/api/link/dashboard.ts Normal file
View File

@ -0,0 +1,12 @@
import server from '@/utils/request';
export const dashboard = (data: object) =>
server.post(`/dashboard/_multi`, data);
export const productCount = (data: object) =>
server.post(`/device-product/_count`, data);
export const getGeo = (data: object) =>
server.post(`/geo/object/device/_search/geo.json`, data);
export const deviceCount = (data: object) =>
server.get(`/device/instance/_count`, data);
export const serverNode = (data: object) =>
server.get(`/dashboard/cluster/nodes`, data);

View File

@ -0,0 +1,189 @@
<template>
<a-spin :spinning="loading">
<div class="dash-board">
<div class="header">
<h3>CPU使用率趋势</h3>
<a-range-picker
@change="pickerTimeChange"
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time"
>
<template #suffixIcon><a-icon type="calendar" /></template>
<template #renderExtraFooter>
<a-radio-group
default-value="a"
button-style="solid"
style="margin-right: 10px"
v-model:value="data.type"
>
<a-radio-button value="hour">
最近1小时
</a-radio-button>
<a-radio-button value="today">
今日
</a-radio-button>
<a-radio-button value="week">
近一周
</a-radio-button>
</a-radio-group></template
>
</a-range-picker>
</div>
<div ref="chartRef" style="width: 100%; height: 300px"></div>
</div>
</a-spin>
</template>
m
<script lang="ts" setup name="Cpu">
import * as echarts from 'echarts';
import { dashboard } from '@/api/link/dashboard';
import moment from 'moment';
import {
getTimeFormat,
getTimeByType,
arrayReverse,
defulteParamsData,
areaStyleCpu,
typeDataLine,
} from './tool.ts';
const chartRef = ref<Record<string, any>>({});
const loading = ref(false);
const data = ref({
type: 'hour',
time: [null, null],
});
const pickerTimeChange = () => {
data.value.type = undefined;
};
const getCPUEcharts = async (val) => {
loading.value = true;
const res = await dashboard(defulteParamsData('cpu', val));
if (res.success) {
const _cpuOptions = {};
const _cpuXAxis = new Set();
if (res.result?.length) {
res.result.forEach((item) => {
const value = item.data.value;
const nodeID = item.data.clusterNodeId;
_cpuXAxis.add(
moment(value.timestamp).format(
getTimeFormat(data.value.type),
),
);
if (!_cpuOptions[nodeID]) {
_cpuOptions[nodeID] = [];
}
_cpuOptions[nodeID].push(
Number(value.cpuSystemUsage).toFixed(2),
);
});
}
handleCpuOptions(_cpuOptions, [..._cpuXAxis.keys()]);
}
setTimeout(() => {
loading.value = false;
}, 300);
};
const setOptions = (optionsData, key) => ({
data: arrayReverse(optionsData[key]),
name: key,
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: areaStyleCpu,
});
const handleCpuOptions = (optionsData, xAxis) => {
const chart = chartRef.value;
if (chart) {
const myChart = echarts.init(chart);
const dataKeys = Object.keys(optionsData);
const options = {
xAxis: {
type: 'category',
boundaryGap: false,
data: arrayReverse(xAxis),
},
tooltip: {
trigger: 'axis',
valueFormatter: (value) => `${value}%`,
},
yAxis: {
type: 'value',
},
grid: {
left: '50px',
right: '50px',
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
color: ['#2CB6E0'],
series: dataKeys.length
? dataKeys.map((key) => setOptions(optionsData, key))
: typeDataLine,
};
myChart.setOption(options);
window.addEventListener('resize', function () {
myChart.resize();
});
}
};
watch(
() => data.value.type,
(val) => {
const endTime = moment(new Date());
const startTime = getTimeByType(val);
data.value.time = [startTime, endTime];
},
{ immediate: true, deep: true },
);
watch(
() => data.value,
(val) => {
const { time } = val;
if (time && Array.isArray(time) && time.length === 2 && time[0]) {
getCPUEcharts(val);
}
},
{ immediate: true, deep: true },
);
</script>
<style lang="less" scoped>
.dash-board {
display: flex;
flex-direction: column;
height: 100%;
padding: 24px;
background-color: #fff;
box-shadow: 0px 2.73036px 5.46071px rgba(31, 89, 245, 0.2);
border-radius: 2px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
width: 200px;
margin-top: 8px;
}
}
</style>

View File

@ -0,0 +1,196 @@
<template>
<a-spin :spinning="loading">
<div class="dash-board">
<div class="header">
<h3>JVM内存使用率趋势</h3>
<a-range-picker
@change="pickerTimeChange"
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time"
>
<template #suffixIcon><a-icon type="calendar" /></template>
<template #renderExtraFooter>
<a-radio-group
default-value="a"
button-style="solid"
style="margin-right: 10px"
v-model:value="data.type"
>
<a-radio-button value="hour">
最近1小时
</a-radio-button>
<a-radio-button value="today">
今日
</a-radio-button>
<a-radio-button value="week">
近一周
</a-radio-button>
</a-radio-group></template
>
</a-range-picker>
</div>
<div ref="chartRef" style="width: 100%; height: 300px"></div>
</div>
</a-spin>
</template>
<script lang="ts" setup name="Jvm">
import * as echarts from 'echarts';
import { dashboard } from '@/api/link/dashboard';
import moment from 'moment';
import {
getTimeFormat,
getTimeByType,
arrayReverse,
typeDataLine,
areaStyleJvm,
defulteParamsData,
} from './tool.ts';
const chartRef = ref<Record<string, any>>({});
const loading = ref(false);
const data = ref({
type: 'hour',
time: [null, null],
});
const pickerTimeChange = () => {
data.value.type = undefined;
};
const getJVMEcharts = async (val) => {
loading.value = true;
const res = await dashboard(defulteParamsData('jvm', val));
if (res.success) {
const _jvmOptions = {};
const _jvmXAxis = new Set();
if (res.result?.length) {
res.result.forEach((item) => {
const value = item.data.value;
const memoryJvmHeapFree = value.memoryJvmHeapFree;
const memoryJvmHeapTotal = value.memoryJvmHeapTotal;
const nodeID = item.data.clusterNodeId;
const _value = (
((memoryJvmHeapTotal - memoryJvmHeapFree) /
memoryJvmHeapTotal) *
100
).toFixed(2);
if (!_jvmOptions[nodeID]) {
_jvmOptions[nodeID] = [];
}
_jvmXAxis.add(
moment(value.timestamp).format(
getTimeFormat(data.value.type),
),
);
_jvmOptions[nodeID].push(_value);
});
}
handleJVMOptions(_jvmOptions, [..._jvmXAxis.keys()]);
}
setTimeout(() => {
loading.value = false;
}, 300);
};
const setOptions = (optionsData, key) => ({
data: arrayReverse(optionsData[key]),
name: key,
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: areaStyleJvm,
});
const handleJVMOptions = (optionsData, xAxis) => {
const chart = chartRef.value;
if (chart) {
const myChart = echarts.init(chart);
const dataKeys = Object.keys(optionsData);
console.log(21, arrayReverse(xAxis), xAxis);
const options = {
xAxis: {
type: 'category',
boundaryGap: false,
data: arrayReverse(xAxis),
},
tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => `${value}%`,
},
yAxis: {
type: 'value',
},
grid: {
left: '50px',
right: '50px',
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
color: ['#60DFC7'],
series: dataKeys.length
? dataKeys.map((key) => setOptions(optionsData, key))
: typeDataLine,
};
myChart.setOption(options);
window.addEventListener('resize', function () {
myChart.resize();
});
}
};
watch(
() => data.value.type,
(val) => {
const endTime = moment(new Date());
const startTime = getTimeByType(val);
data.value.time = [startTime, endTime];
},
{ immediate: true, deep: true },
);
watch(
() => data.value,
(val) => {
const { time } = val;
if (time && Array.isArray(time) && time.length === 2 && time[0]) {
getJVMEcharts(val);
}
},
{ immediate: true, deep: true },
);
</script>
<style lang="less" scoped>
.dash-board {
display: flex;
flex-direction: column;
height: 100%;
padding: 24px;
background-color: #fff;
box-shadow: 0px 2.73036px 5.46071px rgba(31, 89, 245, 0.2);
border-radius: 2px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
width: 200px;
margin-top: 8px;
}
}
</style>

View File

@ -0,0 +1,222 @@
<template>
<a-spin :spinning="loading">
<div class="dash-board">
<div class="header">
<div class="left">
<h3 style="width: 80px">网络流量</h3>
<a-radio-group
button-style="solid"
v-model:value="data.type"
>
<a-radio-button value="bytesRead">
上行
</a-radio-button>
<a-radio-button value="bytesSent">
下行
</a-radio-button>
</a-radio-group>
</div>
<div class="right">
<a-radio-group
default-value="a"
button-style="solid"
style="margin-right: 10px"
v-model:value="data.time.type"
>
<a-radio-button value="hour">
最近1小时
</a-radio-button>
<a-radio-button value="today"> 今日 </a-radio-button>
<a-radio-button value="week"> 近一周 </a-radio-button>
</a-radio-group>
<a-range-picker
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time.time"
@change="pickerTimeChange"
>
<template #suffixIcon
><a-icon type="calendar"
/></template>
</a-range-picker>
</div>
</div>
<div>
<div
ref="chartRef"
v-if="flag"
style="width: 100%; height: 350px"
></div>
<a-empty v-else style="height: 300px; margin-top: 120px" />
</div>
</div>
</a-spin>
</template>
<script lang="ts" setup name="Network">
import { dashboard } from '@/api/link/dashboard';
import {
getTimeByType,
typeDataLine,
areaStyle,
networkParams,
arrayReverse,
} from './tool.ts';
import moment from 'moment';
import * as echarts from 'echarts';
const chartRef = ref<Record<string, any>>({});
const flag = ref(true);
const loading = ref(false);
const myChart = ref(null);
const data = ref({
type: 'bytesRead',
time: {
type: 'today',
time: [null, null],
},
});
const pickerTimeChange = () => {
data.value.time.type = undefined;
};
const getNetworkEcharts = async (val) => {
loading.value = true;
const resp = await dashboard(networkParams(val));
if (resp.success) {
const _networkOptions = {};
const _networkXAxis = new Set();
if (resp.result.length) {
resp.result.forEach((item) => {
const value = item.data.value;
const _data = [];
const nodeID = item.data.clusterNodeId;
value.forEach((item) => {
_data.push(item.value);
_networkXAxis.add(item.timeString);
});
_networkOptions[nodeID] = {
_data: _networkOptions[nodeID]
? _networkOptions[nodeID]._data.concat(_data)
: _data,
};
});
handleNetworkOptions(_networkOptions, [..._networkXAxis.keys()]);
} else {
handleNetworkOptions([], []);
}
}
setTimeout(() => {
loading.value = false;
}, 300);
};
const networkValueRender = (obj) => {
const { value } = obj;
let _data = '';
if (value >= 1024 && value < 1024 * 1024) {
_data = `${Number((value / 1024).toFixed(2))}KB`;
} else if (value >= 1024 * 1024) {
_data = `${Number((value / 1024 / 1024).toFixed(2))}M`;
} else {
_data = `${value}B`;
}
return `${obj?.axisValueLabel}<br />${obj?.marker}${obj?.seriesName}: ${_data}`;
};
const setOptions = (data, key) => ({
data: data[key]._data, // .map((item) => Number((item / 1024 / 1024).toFixed(2))),
name: key,
type: 'line',
smooth: true,
areaStyle,
});
const handleNetworkOptions = (optionsData, xAxis) => {
const chart = chartRef.value;
if (chart) {
const myChart = echarts.init(chart);
const dataKeys = Object.keys(optionsData);
const options = {
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxis,
},
yAxis: {
type: 'value',
},
grid: {
left: '80px',
right: '50px',
},
tooltip: {
trigger: 'axis',
formatter: (_value) => networkValueRender(_value[0]),
},
color: ['#979AFF'],
series: dataKeys.length
? dataKeys.map((key) => setOptions(optionsData, key))
: typeDataLine,
};
myChart.setOption(options);
window.addEventListener('resize', function () {
myChart.resize();
});
}
};
watch(
() => data.value.time.type,
(value) => {
const endTime = moment(new Date());
const startTime = getTimeByType(value);
data.value.time.time = [startTime, endTime];
},
{ immediate: true, deep: true },
);
watch(
() => data.value,
(value) => {
const {
time: { time },
} = value;
if (time && Array.isArray(time) && time.length === 2 && time[0]) {
getNetworkEcharts(value);
}
},
{ immediate: true, deep: true },
);
// onMounted(() => {
// createEcharts();
// });
</script>
<style lang="less" scoped>
.dash-board {
display: flex;
flex-direction: column;
height: 100%;
padding: 24px;
background-color: #fff;
box-shadow: 0px 2.73036px 5.46071px rgba(31, 89, 245, 0.2);
border-radius: 2px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
.left h3 {
width: 200px;
margin-top: 8px;
}
}
.left,
.right {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,155 @@
<template>
<div>
<a-select
style="width: 300px; margin-bottom: 20px"
@change="serverIdChange"
:value="serverId"
:options="serverNode"
v-if="serverNode.length > 1"
></a-select>
<div class="dash-board">
<div class="dash-board-item">
<TopEchartsItemNode title="CPU使用率" :value="topValues.cpu" />
</div>
<div class="dash-board-item">
<TopEchartsItemNode
title="JVM内存"
:max="topValues.jvmTotal"
:bottom="`总JVM内存 ${topValues.jvmTotal}G`"
formatter="G"
:value="topValues.jvm"
/>
</div>
<div class="dash-board-item">
<TopEchartsItemNode
title="磁盘占用"
:max="topValues.usageTotal"
:bottom="`总磁盘大小 ${topValues.usageTotal}G`"
formatter="G"
:value="topValues.usage"
/>
</div>
<div class="dash-board-item">
<TopEchartsItemNode
title="系统内存"
:max="topValues.systemUsageTotal"
:bottom="`系统内存 ${topValues.systemUsageTotal}G`"
formatter="G"
:value="topValues.systemUsage"
/>
</div>
</div>
</div>
</template>
<script>
import { serverNode } from '@/api/link/dashboard';
import TopEchartsItemNode from './TopEchartsItemNode.vue';
import { getWebSocket } from '@/utils/websocket';
import { map } from 'rxjs/operators';
export default {
name: 'TopCard',
components: { TopEchartsItemNode },
data() {
return {
serverId: '',
serverNode: [],
topValues: {
cpu: 0,
jvm: 0,
jvmTotal: 0,
usage: 0,
usageTotal: 0,
systemUsage: 0,
systemUsageTotal: 0,
},
};
},
created() {
serverNode().then((resp) => {
if (resp.success) {
this.serverNode = resp.result.map((item) => ({
label: item.name,
value: item.id,
}));
if (this.serverNode && this.serverNode.length) {
this.serverId = this.serverNode[0].value;
}
}
});
},
watch: {
serverId: {
handler(val) {
if (val) {
this.getData(val);
}
},
immediate: true,
},
},
methods: {
serverIdChange(val) {
this.serverId = val;
},
getData() {
const id = 'operations-statistics-system-info-realTime';
const topic = '/dashboard/systemMonitor/stats/info/realTime';
getWebSocket(id, topic, {
type: 'all',
serverNodeId: this.serverId,
interval: '1s',
agg: 'avg',
})
.pipe(map((res) => res.payload))
.subscribe((payload) => {
const value = payload.value;
const cpu = value.cpu;
const memory = value.memory;
const disk = value.disk;
this.topValues = {
cpu: cpu.systemUsage,
jvm: Number(
(
(memory.jvmHeapUsage / 100) *
(memory.jvmHeapTotal / 1024)
).toFixed(1),
),
jvmTotal: Math.ceil(memory.jvmHeapTotal / 1024),
usage: Number(
((disk.total / 1024) * (disk.usage / 100)).toFixed(
1,
),
),
usageTotal: Math.ceil(disk.total / 1024),
systemUsage: Number(
(
(memory.systemTotal / 1024) *
(memory.systemUsage / 100)
).toFixed(1),
),
systemUsageTotal: Math.ceil(memory.systemTotal / 1024),
};
});
},
},
};
</script>
<style lang="less" scoped>
.dash-board {
display: flex;
flex-wrap: wrap;
height: 100%;
background-color: #fff;
box-shadow: 0px 2.73036px 5.46071px rgba(31, 89, 245, 0.2);
border-radius: 2px;
justify-content: space-between;
.dash-board-item {
flex: 1;
margin: 24px 12px;
min-width: 250px;
}
}
</style>

View File

@ -0,0 +1,282 @@
<template>
<div class="echarts-item">
<div class="echarts-item-left">
<div class="echarts-item-title">{{ title }}</div>
<div class="echarts-item-value">
{{ value || 0 }} {{ formatter || '%' }}
</div>
<div v-if="!!bottom" class="echarts-item-bottom">{{ bottom }}</div>
</div>
<div class="echarts-item-right">
<div ref="chartRef" style="width: 100%; height: 100px"></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'TopEchartsItemNode',
props: {
title: {
type: String,
default: '',
},
value: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 0,
},
bottom: {
type: String,
default: '',
},
formatter: {
type: String,
default: '%',
},
},
data() {
return {
options: {},
};
},
methods: {
createChart(val) {
const chart = this.$refs.chartRef;
if (chart && Object.keys(val).length > 0) {
const myChart = echarts.init(chart);
myChart.setOption(val);
window.addEventListener('resize', function () {
myChart.resize();
});
}
},
getOptions(max, formatter, val) {
let formatterCount = 0;
this.options = {
series: [
{
type: 'gauge',
min: 0,
max: max || 100,
startAngle: 200,
endAngle: -20,
center: ['50%', '67%'],
title: {
show: false,
},
axisTick: {
distance: -20,
lineStyle: {
width: 1,
color: 'rgba(0,0,0,0.15)',
},
},
splitLine: {
distance: -22,
length: 9,
lineStyle: {
width: 1,
color: '#000',
},
},
axisLabel: {
distance: -22,
color: 'auto',
fontSize: 12,
width: 30,
padding: [6, 10, 0, 10],
formatter: (value) => {
formatterCount += 1;
if ([1, 3, 6, 9, 11].includes(formatterCount)) {
return value + (formatter || '%');
}
return '';
},
},
pointer: {
length: '80%',
width: 4,
itemStyle: {
color: 'auto',
},
},
anchor: {
show: true,
showAbove: true,
size: 20,
itemStyle: {
borderWidth: 3,
borderColor: '#fff',
shadowBlur: 20,
shadowColor: 'rgba(0, 0, 0, .25)',
color: 'auto',
},
},
axisLine: {
lineStyle: {
width: 10,
color: [
[0.25, 'rgba(36, 178, 118, 1)'],
[
0.4,
new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(66, 147, 255, 1)',
},
{
offset: 1,
color: 'rgba(36, 178, 118, 1)',
},
],
),
],
[
0.5,
new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(250, 178, 71, 1)',
},
{
offset: 1,
color: 'rgba(66, 147, 255, 1)',
},
],
),
],
[
1,
new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(250, 178, 71, 1)',
},
{
offset: 1,
color: 'rgba(247, 111, 93, 1)',
},
],
),
],
],
},
},
detail: {
show: false,
},
data: [{ value: val || 0 }],
},
],
};
},
},
watch: {
options: {
handler(val) {
this.createChart(val);
},
immediate: true,
deep: true,
},
max: {
handler(val) {
this.getOptions(val, this.formatter, this.value);
},
immediate: true,
deep: true,
},
value: {
handler(val) {
this.getOptions(this.max, this.formatter, val);
},
immediate: true,
deep: true,
},
formatter: {
handler(val) {
this.getOptions(this.max, val, this.value);
},
immediate: true,
deep: true,
},
},
};
</script>
<style lang="less" scoped>
.echarts-item {
display: flex;
height: 150px;
padding: 16px;
background-color: #fff;
box-shadow: 0px 2.73036px 5.46071px rgba(31, 89, 245, 0.2);
.echarts-item-left {
display: flex;
flex-direction: column;
width: 45%;
}
.echarts-item-right {
width: 55%;
}
.echarts-item-title {
margin-bottom: 8px;
color: rgba(#000, 0.6);
font-size: 16px;
}
.echarts-item-value {
font-weight: bold;
font-size: 36px;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-align: left;
text-overflow: ellipsis;
}
.echarts-item-bottom {
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
height: 0;
padding-left: 12px;
&::before {
position: absolute;
top: 50%;
left: 0;
width: 4px;
height: 12px;
background-color: #ff595e;
transform: translateY(-50%);
content: ' ';
}
}
}
</style>

View File

@ -0,0 +1,125 @@
import moment from 'moment';
import * as echarts from 'echarts';
export const getInterval = (type) => {
switch (type) {
case 'year':
return '30d';
case 'month':
case 'week':
return '1d';
case 'hour':
return '1m';
default:
return '1h';
}
};
export const getTimeFormat = (type) => {
switch (type) {
case 'year':
return 'YYYY-MM-DD';
case 'month':
case 'week':
return 'MM-DD';
case 'hour':
return 'HH:mm';
default:
return 'HH';
}
};
export const getTimeByType = (type) => {
switch (type) {
case 'hour':
return moment().subtract(1, 'hours');
case 'week':
return moment().subtract(6, 'days');
case 'month':
return moment().subtract(29, 'days');
case 'year':
return moment().subtract(365, 'days');
default:
return moment().startOf('day');
}
};
export const arrayReverse = (data) => {
const newArray = [];
for (let i = data.length - 1; i >= 0; i--) {
newArray.push(data[i]);
}
return newArray;
};
export const networkParams = (val) => [
{
dashboard: 'systemMonitor',
object: 'network',
measurement: 'traffic',
dimension: 'agg',
group: 'network',
params: {
type: val.type,
interval: getInterval(val.time.type),
from: moment(val.time.time[0]).valueOf(),
to: moment(val.time.time[1]).valueOf(),
},
},
];
export const defulteParamsData = (group, val) => [
{
dashboard: 'systemMonitor',
object: 'stats',
measurement: 'info',
dimension: 'history',
group,
params: {
from: moment(val.time[0]).valueOf(),
to: moment(val.time[1]).valueOf(),
},
},
];
export const areaStyle = {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: 'rgba(151, 154, 255, 0)',
},
{
offset: 0,
color: 'rgba(151, 154, 255, .24)',
},
]),
};
export const areaStyleCpu = {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: 'rgba(44, 182, 224, 0)',
},
{
offset: 0,
color: 'rgba(44, 182, 224, .24)',
},
]),
};
export const areaStyleJvm = {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: 'rgba(96, 223, 199, 0)',
},
{
offset: 0,
color: 'rgba(96, 223, 199, .24)',
},
]),
};
export const typeDataLine = [
{
data: [],
type: 'line',
},
];

View File

@ -0,0 +1,25 @@
<template>
<page-container>
<div>
<a-row :gutter="[24, 24]">
<a-col :span="24"><TopCard /> </a-col>
<a-col :span="24"><Network /></a-col>
<a-col :span="12"><Cpu /></a-col>
<a-col :span="12"><Jvm /></a-col>
</a-row>
</div>
</page-container>
</template>
<script>
import TopCard from './components/TopCard.vue';
import Network from './components/Network.vue';
import Cpu from './components/Cpu.vue';
import Jvm from './components/Jvm.vue';
export default {
name: 'LinkDashboard',
components: { Cpu, Jvm, Network, TopCard },
};
</script>
<style lang="less" scoped></style>