feat: 🚀 服务监控CPU占用和磁盘占用

This commit is contained in:
bunny 2024-10-25 16:52:06 +08:00
parent d92f7ae073
commit e873d138dd
11 changed files with 541 additions and 32 deletions

View File

@ -70,6 +70,7 @@
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"echarts-gl": "^2.0.9",
"el-table-infinite-scroll": "^3.0.3", "el-table-infinite-scroll": "^3.0.3",
"element-plus": "2.7.1", "element-plus": "2.7.1",
"intro.js": "^7.2.0", "intro.js": "^7.2.0",

View File

@ -71,6 +71,9 @@ importers:
echarts: echarts:
specifier: ^5.5.0 specifier: ^5.5.0
version: 5.5.1 version: 5.5.1
echarts-gl:
specifier: ^2.0.9
version: 2.0.9(echarts@5.5.1)
el-table-infinite-scroll: el-table-infinite-scroll:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.6(typescript@5.5.4) version: 3.0.6(typescript@5.5.4)
@ -2049,6 +2052,9 @@ packages:
cjs-module-lexer@1.4.0: cjs-module-lexer@1.4.0:
resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==} resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==}
claygl@1.3.0:
resolution: {integrity: sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ==}
cli-boxes@3.0.0: cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -2602,6 +2608,11 @@ packages:
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
echarts-gl@2.0.9:
resolution: {integrity: sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==}
peerDependencies:
echarts: ^5.1.2
echarts@5.5.1: echarts@5.5.1:
resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==} resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==}
@ -7912,6 +7923,8 @@ snapshots:
cjs-module-lexer@1.4.0: {} cjs-module-lexer@1.4.0: {}
claygl@1.3.0: {}
cli-boxes@3.0.0: {} cli-boxes@3.0.0: {}
cli-cursor@3.1.0: cli-cursor@3.1.0:
@ -8511,6 +8524,12 @@ snapshots:
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
echarts-gl@2.0.9(echarts@5.5.1):
dependencies:
claygl: 1.3.0
echarts: 5.5.1
zrender: 5.6.0
echarts@5.5.1: echarts@5.5.1:
dependencies: dependencies:
tslib: 2.3.0 tslib: 2.3.0

View File

@ -14,3 +14,23 @@ export const fetchSystemInfo = () => {
export const fetchSystemCaches = () => { export const fetchSystemCaches = () => {
return http.request<any>('get', 'actuator/caches'); return http.request<any>('get', 'actuator/caches');
}; };
/** actuator断端点-CPU占用 */
export const fetchSystemCPU = () => {
return http.request<any>('get', 'actuator/metrics/system.cpu.usage');
};
/** actuator断端点-CPU占用 */
export const fetchSystemProcessCPU = () => {
return http.request<any>('get', 'actuator/metrics/process.cpu.usage');
};
/** actuator断端点-磁盘可用 */
export const fetchSystemDiskFree = () => {
return http.request<any>('get', 'actuator/metrics/disk.free');
};
/** actuator断端点-磁盘总量 */
export const fetchSystemDiskTotal = () => {
return http.request<any>('get', 'actuator/metrics/disk.total');
};

View File

@ -8,6 +8,7 @@ import router from './router';
import { useElementPlus } from '@/plugins/elementPlus'; import { useElementPlus } from '@/plugins/elementPlus';
import { injectResponsiveStorage } from '@/utils/responsive'; import { injectResponsiveStorage } from '@/utils/responsive';
import { createApp, type Directive } from 'vue'; import { createApp, type Directive } from 'vue';
import * as echarts from 'echarts';
import Table from '@pureadmin/table'; import Table from '@pureadmin/table';
// import PureDescriptions from "@pureadmin/descriptions"; // import PureDescriptions from "@pureadmin/descriptions";
@ -45,7 +46,7 @@ app.component('IconifyIconOnline', IconifyIconOnline);
app.component('FontIcon', FontIcon); app.component('FontIcon', FontIcon);
app.component('Auth', Auth); app.component('Auth', Auth);
app.component('Perms', Perms); app.component('Perms', Perms);
app.config.globalProperties.$echarts = echarts;
app.use(VueTippy); app.use(VueTippy);
getPlatformConfig(app).then(async config => { getPlatformConfig(app).then(async config => {

View File

@ -1,36 +1,26 @@
import type { App } from "vue"; import type { App } from 'vue';
import * as echarts from "echarts/core"; import * as echarts from 'echarts/core';
import { PieChart, BarChart, LineChart } from "echarts/charts"; import { BarChart, LineChart, PieChart } from 'echarts/charts';
import { CanvasRenderer, SVGRenderer } from "echarts/renderers"; import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
import { import { DataZoomComponent, GraphicComponent, GridComponent, LegendComponent, PolarComponent, TitleComponent, ToolboxComponent, TooltipComponent, VisualMapComponent } from 'echarts/components';
GridComponent,
TitleComponent,
PolarComponent,
LegendComponent,
GraphicComponent,
ToolboxComponent,
TooltipComponent,
DataZoomComponent,
VisualMapComponent
} from "echarts/components";
const { use } = echarts; const { use } = echarts;
use([ use([
PieChart, PieChart,
BarChart, BarChart,
LineChart, LineChart,
CanvasRenderer, CanvasRenderer,
SVGRenderer, SVGRenderer,
GridComponent, GridComponent,
TitleComponent, TitleComponent,
PolarComponent, PolarComponent,
LegendComponent, LegendComponent,
GraphicComponent, GraphicComponent,
ToolboxComponent, ToolboxComponent,
TooltipComponent, TooltipComponent,
DataZoomComponent, DataZoomComponent,
VisualMapComponent VisualMapComponent,
]); ]);
/** /**
@ -38,7 +28,7 @@ use([
* @see `$echarts` `globalProperties` https://pure-admin-utils.netlify.app/hooks/useECharts/useECharts#%E4%BD%BF%E7%94%A8%E5%89%8D%E6%8F%90 * @see `$echarts` `globalProperties` https://pure-admin-utils.netlify.app/hooks/useECharts/useECharts#%E4%BD%BF%E7%94%A8%E5%89%8D%E6%8F%90
*/ */
export function useEcharts(app: App) { export function useEcharts(app: App) {
app.config.globalProperties.$echarts = echarts; app.config.globalProperties.$echarts = echarts;
} }
export default echarts; export default echarts;

View File

@ -35,7 +35,6 @@ const onChange = (uploadFile: any) => {
reader.onload = e => { reader.onload = e => {
imgBase64Src.value = e.target.result as string; imgBase64Src.value = e.target.result as string;
isShow.value = true; isShow.value = true;
console.log(imgBase64Src.value);
}; };
reader.readAsDataURL(uploadFile.raw); reader.readAsDataURL(uploadFile.raw);
}; };

View File

@ -4,6 +4,9 @@ import SystemInfo from '@/views/monitor/server/system-info.vue';
import { svg } from '@/views/monitor/server/utils/columns'; import { svg } from '@/views/monitor/server/utils/columns';
import { info, loading, onSearch } from '@/views/monitor/server/utils/hooks'; import { info, loading, onSearch } from '@/views/monitor/server/utils/hooks';
import SystemServer from '@/views/monitor/server/system-server.vue'; import SystemServer from '@/views/monitor/server/system-server.vue';
import SystemCpu from '@/views/monitor/server/system-cpu.vue';
import SystemJvmCpu from '@/views/monitor/server/system-jvm-cpu.vue';
import SystemDisk from '@/views/monitor/server/system-disk.vue';
onMounted(() => { onMounted(() => {
onSearch(); onSearch();
@ -15,6 +18,9 @@ onMounted(() => {
<el-row :gutter="16"> <el-row :gutter="16">
<system-info v-if="info" :info="info" /> <system-info v-if="info" :info="info" />
<system-server /> <system-server />
<system-cpu />
<system-jvm-cpu />
<system-disk />
</el-row> </el-row>
</div> </div>
</template> </template>

View File

@ -0,0 +1,177 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { UtilsEChartsOption } from '@pureadmin/utils';
// import 'echarts-gl';
import { cardClass, cardLogoClass } from '@/views/monitor/server/utils/columns';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import * as echarts from 'echarts';
import { useIntervalFn } from '@vueuse/core';
import dayjs from 'dayjs';
import { fetchSystemCPU } from '@/api/v1/actuator';
const cupECharts = ref();
const myChart = ref<any>();
// ECharts
const seriesData = ref([]);
const xSeriesData = ref([]);
const option = reactive<UtilsEChartsOption>({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
extraCssText: 'width: 170px',
},
legend: {
top: '0%',
left: 'center',
},
grid: {
//
top: '20px',
right: '20px',
bottom: '20px',
},
xAxis: {
type: 'category',
boundaryGap: false,
inverse: true,
},
yAxis: {
type: 'value',
},
series: [
{
data: seriesData.value,
type: 'line',
areaStyle: {},
animationDelay: function (idx) {
return idx * 10; //
},
animationDuration: 1000, //
},
],
animationDuration: 666,
animationDurationUpdate: 200,
animationEasing: 'linear',
animationEasingUpdate: 'linear',
});
/** 初始化数据 */
const onSearch = async () => {
//
const result = await fetchSystemCPU();
const value = result.measurements[0].value ?? 0;
// 10
if (seriesData.value.length > 5) {
seriesData.value = seriesData.value.slice(-5);
xSeriesData.value = xSeriesData.value.slice(-5);
}
seriesData.value.push(value * 100);
xSeriesData.value.push(dayjs().format('mm:ss'));
myChart.value.setOption({
xAxis: { data: xSeriesData.value },
series: [{ type: 'line', data: seriesData.value }],
});
};
onMounted(() => {
// eacharts
myChart.value = echarts.init(cupECharts.value);
option && myChart.value.setOption(option);
onSearch();
//
useIntervalFn(() => onSearch(), 1000);
});
</script>
<template>
<el-col :lg="6" :md="8" :sm="12" :xl="4" :xs="24">
<div :class="cardClass">
<div class="list-card-item_detail bg-bg_color">
<el-row justify="space-between">
<div :class="cardLogoClass">
<component :is="useRenderIcon('solar:cpu-bold-duotone')" />
</div>
</el-row>
<p class="list-card-item_detail--name text-text_color_primary">服务CPU使用率</p>
<div ref="cupECharts" style="width: 100%; height: 230px" />
</div>
</div>
</el-col>
</template>
<style lang="scss" scoped>
.list-card-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
overflow: hidden;
cursor: pointer;
border-radius: 3px;
&_detail {
flex: 1;
min-height: 140px;
padding: 24px 32px;
&--logo {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
font-size: 26px;
color: #0052d9;
background: #e0ebff;
border-radius: 50%;
&__disabled {
color: #a1c4ff;
}
}
&--operation {
display: flex;
height: 100%;
&--tag {
border: 0;
}
}
&--name {
margin: 24px 0 8px;
font-size: 16px;
font-weight: 400;
}
&--desc {
display: -webkit-box;
height: 40px;
margin-bottom: 24px;
overflow: hidden;
font-size: 12px;
line-height: 20px;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
&__disabled {
.list-card-item_detail--name {
color: var(--el-text-color-disabled);
}
.list-card-item_detail--operation--tag {
color: #bababa;
}
}
}
</style>

View File

@ -0,0 +1,118 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
// import 'echarts-gl';
import { cardClass, cardLogoClass } from '@/views/monitor/server/utils/columns';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { fetchSystemDiskFree, fetchSystemDiskTotal } from '@/api/v1/actuator';
const free = ref();
const total = ref();
const percentage = ref<number>(0);
/** 初始化数据 */
const onSearch = async () => {
//
const diskFree = await fetchSystemDiskFree();
const diskTotal = await fetchSystemDiskTotal();
free.value = diskFree.measurements[0].value / 1024 / 1024 / 1024;
total.value = diskTotal.measurements[0].value / 1024 / 1024 / 1024;
free.value = free.value.toFixed(2);
total.value = total.value.toFixed(2);
percentage.value = ((total.value - free.value) / total.value) * 100;
percentage.value = Number(percentage.value.toFixed(2));
};
onMounted(() => {
onSearch();
});
</script>
<template>
<el-col :lg="6" :md="8" :sm="12" :xl="4" :xs="24">
<div :class="cardClass">
<div class="list-card-item_detail bg-bg_color">
<el-row justify="space-between">
<div :class="cardLogoClass">
<component :is="useRenderIcon('carbon:logo-vmware')" />
</div>
</el-row>
<p class="list-card-item_detail--name text-text_color_primary">磁盘使用</p>
<div class="flex justify-center">
<el-progress :percentage="percentage" type="dashboard" />
</div>
</div>
</div>
</el-col>
</template>
<style lang="scss" scoped>
.list-card-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
overflow: hidden;
cursor: pointer;
border-radius: 3px;
&_detail {
flex: 1;
min-height: 140px;
padding: 24px 32px;
&--logo {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
font-size: 26px;
color: #0052d9;
background: #e0ebff;
border-radius: 50%;
&__disabled {
color: #a1c4ff;
}
}
&--operation {
display: flex;
height: 100%;
&--tag {
border: 0;
}
}
&--name {
margin: 24px 0 8px;
font-size: 16px;
font-weight: 400;
}
&--desc {
display: -webkit-box;
height: 40px;
margin-bottom: 24px;
overflow: hidden;
font-size: 12px;
line-height: 20px;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
&__disabled {
.list-card-item_detail--name {
color: var(--el-text-color-disabled);
}
.list-card-item_detail--operation--tag {
color: #bababa;
}
}
}
</style>

View File

@ -0,0 +1,176 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { UtilsEChartsOption } from '@pureadmin/utils';
// import 'echarts-gl';
import { cardClass, cardLogoClass } from '@/views/monitor/server/utils/columns';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import * as echarts from 'echarts';
import { useIntervalFn } from '@vueuse/core';
import dayjs from 'dayjs';
import { fetchSystemProcessCPU } from '@/api/v1/actuator';
const jvmCPUECharts = ref();
const myChart = ref<any>();
// ECharts
const seriesData = ref([]);
const xSeriesData = ref([]);
const option = reactive<UtilsEChartsOption>({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
extraCssText: 'width: 170px',
},
legend: {
top: '0%',
left: 'center',
},
grid: {
top: '20px',
right: '20px',
bottom: '20px',
},
xAxis: {
type: 'category',
boundaryGap: false,
inverse: true,
},
yAxis: {
type: 'value',
},
series: [
{
data: seriesData.value,
type: 'line',
areaStyle: {},
animationDelay: function (idx) {
return idx * 10; //
},
animationDuration: 1000, //
},
],
animationDuration: 666,
animationDurationUpdate: 200,
animationEasing: 'linear',
animationEasingUpdate: 'linear',
});
/** 初始化数据 */
const onSearch = async () => {
//
const result = await fetchSystemProcessCPU();
const value = result.measurements[0].value;
// 10
if (seriesData.value.length > 5) {
seriesData.value = seriesData.value.slice(-5);
xSeriesData.value = xSeriesData.value.slice(-5);
}
seriesData.value.push(value * 100);
xSeriesData.value.push(dayjs().format('mm:ss'));
myChart.value.setOption({
xAxis: { data: xSeriesData.value },
series: [{ type: 'line', data: seriesData.value }],
});
};
onMounted(() => {
// eacharts
myChart.value = echarts.init(jvmCPUECharts.value);
option && myChart.value.setOption(option);
onSearch();
//
useIntervalFn(() => onSearch(), 1000);
});
</script>
<template>
<el-col :lg="6" :md="8" :sm="12" :xl="4" :xs="24">
<div :class="cardClass">
<div class="list-card-item_detail bg-bg_color">
<el-row justify="space-between">
<div :class="cardLogoClass">
<component :is="useRenderIcon('carbon:logo-vmware')" />
</div>
</el-row>
<p class="list-card-item_detail--name text-text_color_primary">JVM最近cpu使用率</p>
<div ref="jvmCPUECharts" style="width: 100%; height: 230px" />
</div>
</div>
</el-col>
</template>
<style lang="scss" scoped>
.list-card-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
overflow: hidden;
cursor: pointer;
border-radius: 3px;
&_detail {
flex: 1;
min-height: 140px;
padding: 24px 32px;
&--logo {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
font-size: 26px;
color: #0052d9;
background: #e0ebff;
border-radius: 50%;
&__disabled {
color: #a1c4ff;
}
}
&--operation {
display: flex;
height: 100%;
&--tag {
border: 0;
}
}
&--name {
margin: 24px 0 8px;
font-size: 16px;
font-weight: 400;
}
&--desc {
display: -webkit-box;
height: 40px;
margin-bottom: 24px;
overflow: hidden;
font-size: 12px;
line-height: 20px;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
&__disabled {
.list-card-item_detail--name {
color: var(--el-text-color-disabled);
}
.list-card-item_detail--operation--tag {
color: #bababa;
}
}
}
</style>

View File

@ -272,6 +272,8 @@ export const onResetPassword = (row: any) => {
// 更新成功关闭弹窗 // 更新成功关闭弹窗
if (!result) return; if (!result) return;
restPasswordForm.password = '';
restPasswordForm.userId = undefined;
done(); done();
} }
}); });