1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat: 监控前端界面显示

This commit is contained in:
ssongliu 2022-09-08 12:06:53 +08:00 committed by ssongliu
parent 4ced069428
commit ade1e0cea2
13 changed files with 760 additions and 10378 deletions

View File

@ -1,6 +1,6 @@
system:
port: 9999
db_type: mysql
db_type: sqlite
level: debug
jwt:

10492
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
export namespace Monitor {
export interface MonitorData {
param: string;
date: Array<Date>;
value: Array<any>;
}
export interface MonitorSearch {
param: string;
info: string;
startTime: Date;
endTime: Date;
}
}

View File

@ -0,0 +1,10 @@
import http from '@/api';
import { Monitor } from '../interface/monitor';
export const loadMonitor = (param: Monitor.MonitorSearch) => {
return http.post<Array<Monitor.MonitorData>>(`/monitors/search`, param);
};
export const getNetworkOptions = () => {
return http.get<Array<string>>(`/monitors/netoptions`);
};

View File

@ -11,6 +11,13 @@ export default {
login: 'Login',
conn: 'Connect',
},
search: {
timeStart: 'Time start',
timeEnd: 'Time end',
timeRange: 'To',
dateStart: 'Date start',
dateEnd: 'Date end',
},
table: {
name: 'Name',
group: 'Group',
@ -77,6 +84,7 @@ export default {
menu: {
home: 'Dashboard',
demo: 'Demo',
monitor: 'Monitor',
terminal: 'Terminal',
operations: 'Operation logs',
files: 'File Management',
@ -105,6 +113,25 @@ export default {
changePassword: 'Change Password',
logout: 'Logout',
},
monitor: {
avgLoad: 'Average load',
loadDetail: 'Load detail',
resourceUsage: 'Resource utilization rate',
min: 'Minutes',
read: 'Read',
write: 'Write',
count: 'Times',
readWriteCount: 'Read or write Times',
readWriteTime: 'Read or write delay',
today: 'Today',
yestoday: 'Yestoday',
lastNDay: 'Last {0} day',
memory: 'Memory',
disk: 'Disk',
network: 'Network',
up: 'Up',
down: 'Down',
},
terminal: {
conn: 'connection',
testConn: 'Test connection',
@ -200,5 +227,8 @@ export default {
downloadSuccess: 'Download successful',
downloadUrl: 'download URL',
downloadStart: 'Download started!',
moveStart: 'Move start',
move: 'Move',
copy: 'Cpoy',
},
};

View File

@ -11,6 +11,13 @@ export default {
conn: '连接',
login: '登录',
},
search: {
timeStart: '开始时间',
timeEnd: '结束时间',
timeRange: '至',
dateStart: '开始日期',
dateEnd: '结束日期',
},
table: {
name: '名称',
group: '组',
@ -78,6 +85,7 @@ export default {
home: '概览',
demo: '样例',
terminal: '终端管理',
monitor: '监控',
operations: '操作记录',
files: '文件管理',
},
@ -105,6 +113,25 @@ export default {
changePassword: '修改密码',
logout: '退出登录',
},
monitor: {
avgLoad: '平均负载',
loadDetail: '负载详情',
resourceUsage: '资源使用率',
min: '分钟',
read: '读取',
write: '写入',
count: '次',
readWriteCount: '读写次数',
readWriteTime: '读写延迟',
today: '今天',
yestoday: '昨天',
lastNDay: ' {0} ',
memory: '内存',
disk: '磁盘',
network: '网络',
up: '上行',
down: '下行',
},
terminal: {
conn: '连接',
testConn: '连接测试',

View File

@ -0,0 +1,28 @@
import { Layout } from '@/routers/constant';
const monitorRouter = {
sort: 2,
path: '/monitors',
component: Layout,
redirect: '/monitor',
meta: {
title: 'menu.monitor',
icon: 'monitor',
},
children: [
{
path: '/monitors/monitor',
name: 'Monitor',
component: () => import('@/views/monitor/index.vue'),
meta: {
requiresAuth: true,
key: 'Monitor',
title: 'menu.monitor',
icon: 'Connection',
activeMenu: '/monitors',
},
},
],
};
export default monitorRouter;

View File

@ -49,6 +49,19 @@ export function dateFromat(row: number, col: number, dataStr: any) {
return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`;
}
export function dateFromatWithoutYear(dataStr: any) {
const date = new Date(dataStr);
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}`;
}
export function getRandomStr(e: number): string {
const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
const a = t.length;

View File

@ -72,7 +72,7 @@ const batchDelete = async (row: User.User | null) => {
} else {
ids.push(row.id);
}
await useDeleteData(deleteUser, { ids: ids }, 'commons.msg.delete');
await useDeleteData(deleteUser, { ids: ids }, 'commons.msg.delete', true);
search();
};
const buttons = [

View File

@ -0,0 +1,502 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="24">
<el-card style="overflow: inherit">
<template #header>
<span style="font-size: 16px; font-weight: 500">{{ $t('monitor.avgLoad') }}</span>
<el-date-picker
@change="search('load')"
v-model="timeRangeLoad"
type="datetimerange"
size="small"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; right: 20px"
>
</el-date-picker>
</template>
<div id="loadLoadChart" style="width: 100%; height: 400px"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="12">
<el-card style="overflow: inherit">
<template #header>
<span style="font-size: 16px; font-weight: 500">CPU</span>
<el-date-picker
@change="search('cpu')"
v-model="timeRangeCpu"
type="datetimerange"
size="small"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; right: 20px"
>
</el-date-picker>
</template>
<div id="loadCPUChart" style="width: 100%; height: 400px"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card style="overflow: inherit">
<template #header>
<span style="font-size: 16px; font-weight: 500">{{ $t('monitor.memory') }}</span>
<el-date-picker
@change="search('memory')"
v-model="timeRangeMemory"
type="datetimerange"
size="small"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; right: 20px"
>
</el-date-picker>
</template>
<div id="loadMemoryChart" style="width: 100%; height: 400px"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="12">
<el-card style="overflow: inherit">
<template #header>
<span style="font-size: 16px; font-weight: 500">{{ $t('monitor.disk') }} IO</span>
<el-date-picker
@change="search('io')"
v-model="timeRangeIO"
type="datetimerange"
size="small"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; right: 20px"
>
</el-date-picker>
</template>
<div id="loadIOChart" style="width: 100%; height: 400px"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card style="overflow: inherit">
<template #header>
<span style="font-size: 16px; font-weight: 500">{{ $t('monitor.network') }} IO</span>
<el-select
v-model="networkChoose"
clearable
filterable
@change="search('network')"
style="margin-left: 20px"
placeholder="Select"
size="small"
>
<el-option v-for="item in netOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-date-picker
@change="search('network')"
v-model="timeRangeNetwork"
type="datetimerange"
size="small"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; right: 20px"
>
</el-date-picker>
</template>
<div id="loadNetworkChart" style="width: 100%; height: 400px"></div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts';
import { loadMonitor, getNetworkOptions } from '@/api/modules/monitor';
import { Monitor } from '@/api/interface/monitor';
import { dateFromatWithoutYear } from '@/utils/util';
import i18n from '@/lang';
const zoomStart = ref();
const monitorBase = ref();
const timeRangeLoad = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const timeRangeCpu = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const timeRangeMemory = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const timeRangeIO = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const timeRangeNetwork = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const networkChoose = ref();
const netOptions = ref();
const shortcuts = [
{
text: i18n.global.t('monitor.today'),
value: () => {
const end = new Date();
const start = new Date(new Date().setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.yestoday'),
value: () => {
const yestoday = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(yestoday.setHours(23, 59, 59, 999));
const start = new Date(yestoday.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [3]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [7]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [30]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const end = new Date();
return [start, end];
},
},
];
const searchTime = ref();
const searchInfo = reactive<Monitor.MonitorSearch>({
param: '',
info: '',
startTime: new Date(new Date().setHours(0, 0, 0, 0)),
endTime: new Date(),
});
const search = async (param: string) => {
searchInfo.param = param;
switch (param) {
case 'load':
searchTime.value = timeRangeLoad.value;
break;
case 'cpu':
searchTime.value = timeRangeCpu.value;
break;
case 'memory':
searchTime.value = timeRangeMemory.value;
break;
case 'io':
searchTime.value = timeRangeIO.value;
break;
case 'network':
searchTime.value = timeRangeNetwork.value;
searchInfo.info = networkChoose.value;
break;
}
if (searchTime.value && searchTime.value.length === 2) {
searchInfo.startTime = searchTime.value[0];
searchInfo.endTime = searchTime.value[1];
}
const res = await loadMonitor(searchInfo);
if (res.data[0].value === null) {
return;
}
monitorBase.value = res.data;
for (const item of monitorBase.value) {
switch (item.param) {
case 'base':
let baseDate = item.date.map(function (item: any) {
return dateFromatWithoutYear(item);
});
if (param === 'cpu' || param === 'all') {
let cpuData = item.value.map(function (item: any) {
return item.cpu.toFixed(2);
});
let yDatasOfCpu = { name: 'CPU', type: 'line', data: cpuData, showSymbol: false };
initCharts('loadCPUChart', baseDate, yDatasOfCpu, 'CPU', '%');
}
if (param === 'memory' || param === 'all') {
let memoryData = item.value.map(function (item: any) {
return item.memory.toFixed(2);
});
let yDatasOfMem = {
name: i18n.global.t('monitor.memory'),
type: 'line',
data: memoryData,
showSymbol: false,
};
initCharts('loadMemoryChart', baseDate, yDatasOfMem, i18n.global.t('monitor.memory'), '%');
}
if (param === 'load' || param === 'all') {
initLoadCharts(item);
}
break;
case 'io':
initIOCharts(item);
break;
case 'network':
let networkDate = item.date.map(function (item: any) {
return dateFromatWithoutYear(item);
});
let networkUp = item.value.map(function (item: any) {
return item.up.toFixed(2);
});
let yDatasOfUp = {
name: i18n.global.t('monitor.up'),
type: 'line',
data: networkUp,
showSymbol: false,
};
let networkOut = item.value.map(function (item: any) {
return item.down.toFixed(2);
});
let yDatasOfDown = {
name: i18n.global.t('monitor.down'),
type: 'line',
data: networkOut,
showSymbol: false,
};
initCharts('loadNetworkChart', networkDate, [yDatasOfUp, yDatasOfDown], 'KB/s', 'KB/s');
}
}
};
const loadNetworkOptions = async () => {
const res = await getNetworkOptions();
netOptions.value = res.data;
searchInfo.info = netOptions.value && netOptions.value[0];
networkChoose.value = searchInfo.info;
search('all');
};
function initCharts(chartName: string, xDatas: any, yDatas: any, yTitle: string, formatStr: string) {
const lineChart = echarts.init(document.getElementById(chartName) as HTMLElement);
const option = {
zlevel: 1,
z: 1,
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
for (const item of datas) {
res += item.marker + ' ' + item.seriesName + '' + item.data + formatStr + '<br/>';
}
return res;
},
},
legend: {
data: chartName === 'loadNetworkChart' && [i18n.global.t('monitor.up'), i18n.global.t('monitor.down')],
},
grid: { left: '7%', right: '7%', bottom: '20%' },
xAxis: { data: xDatas },
yAxis: { name: '( ' + formatStr + ' )' },
dataZoom: [{ startValue: zoomStart.value }],
series: yDatas,
};
lineChart.setOption(option, true);
}
function initLoadCharts(item: Monitor.MonitorData) {
const lineChart = echarts.init(document.getElementById('loadLoadChart') as HTMLElement);
const option = {
zlevel: 1,
z: 1,
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
for (const item of datas) {
res += item.marker + ' ' + item.seriesName + '' + item.data + '%' + '<br/>';
}
return res;
},
},
legend: {
data: [
'1 ' + i18n.global.t('monitor.min'),
'5 ' + i18n.global.t('monitor.min'),
'15 ' + i18n.global.t('monitor.min'),
i18n.global.t('monitor.resourceUsage'),
],
},
grid: { left: '7%', right: '7%', bottom: '20%' },
xAxis: {
data: item.date.map(function (item: any) {
return dateFromatWithoutYear(item);
}),
},
yAxis: [
{ type: 'value', name: i18n.global.t('monitor.loadDetail') + ' ( % )' },
{
type: 'value',
name: i18n.global.t('monitor.resourceUsage') + ' ( % )',
position: 'right',
alignTicks: true,
},
],
dataZoom: [{ startValue: zoomStart.value }],
series: [
{
name: '1 ' + i18n.global.t('monitor.min'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.cpuLoad1.toFixed(2);
}),
},
{
name: '5 ' + i18n.global.t('monitor.min'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.cpuLoad5.toFixed(2);
}),
},
{
name: '15 ' + i18n.global.t('monitor.min'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.cpuLoad15.toFixed(2);
}),
},
{
name: i18n.global.t('monitor.resourceUsage'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.loadUsage.toFixed(2);
}),
yAxisIndex: 1,
},
],
};
lineChart.setOption(option, true);
}
function initIOCharts(item: Monitor.MonitorData) {
const lineChart = echarts.init(document.getElementById('loadIOChart') as HTMLElement);
const option = {
zlevel: 1,
z: 1,
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
for (const item of datas) {
if (
item.seriesName === i18n.global.t('monitor.read') ||
item.seriesName === i18n.global.t('monitor.write')
) {
res += item.marker + ' ' + item.seriesName + '' + item.data + ' KB/s' + '<br/>';
}
if (item.seriesName === i18n.global.t('monitor.readWriteCount')) {
res +=
item.marker +
' ' +
item.seriesName +
'' +
item.data +
' ' +
i18n.global.t('monitor.count') +
'/s' +
'<br/>';
}
if (item.seriesName === i18n.global.t('monitor.readWriteTime')) {
res += item.marker + ' ' + item.seriesName + '' + item.data + ' ms' + '<br/>';
}
}
return res;
},
},
legend: {
data: [
i18n.global.t('monitor.read'),
i18n.global.t('monitor.write'),
i18n.global.t('monitor.readWriteCount'),
i18n.global.t('monitor.readWriteTime'),
],
},
grid: { left: '7%', right: '7%', bottom: '20%' },
xAxis: {
data: item.date.map(function (item: any) {
return dateFromatWithoutYear(item);
}),
},
yAxis: [
{ type: 'value', name: '( KB/s )' },
{ type: 'value', position: 'right', alignTicks: true },
],
dataZoom: [{ startValue: zoomStart.value }],
series: [
{
name: i18n.global.t('monitor.read'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return (item.read / 1024).toFixed(2);
}),
},
{
name: i18n.global.t('monitor.write'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return (item.write / 1024).toFixed(2);
}),
},
{
name: i18n.global.t('monitor.readWriteCount'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.count;
}),
yAxisIndex: 1,
},
{
name: i18n.global.t('monitor.readWriteTime'),
type: 'line',
showSymbol: false,
data: item.value.map(function (item: any) {
return item.time;
}),
yAxisIndex: 1,
},
],
};
lineChart.setOption(option, true);
}
function changeChartSize() {
echarts.getInstanceByDom(document.getElementById('loadLoadChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadCPUChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadMemoryChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadIOChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadNetworkChart') as HTMLElement)?.resize();
}
onMounted(() => {
zoomStart.value = dateFromatWithoutYear(new Date(new Date().setHours(0, 0, 0, 0)));
loadNetworkOptions();
window.addEventListener('resize', changeChartSize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', changeChartSize);
});
</script>

View File

@ -113,7 +113,7 @@ const batchDelete = async (row: Command.CommandInfo | null) => {
} else {
ids.push(row.id);
}
await useDeleteData(deleteCommand, { ids: ids }, 'commons.msg.delete');
await useDeleteData(deleteCommand, { ids: ids }, 'commons.msg.delete', true);
search();
};

View File

@ -265,10 +265,10 @@ const onDelete = async (node: Node, data: Tree) => {
return;
}
if (node.level === 1) {
await useDeleteData(deleteGroup, data.id - 10000, i18n.global.t('terminal.groupDeleteHelper'));
await useDeleteData(deleteGroup, data.id - 10000, i18n.global.t('terminal.groupDeleteHelper'), true);
loadGroups();
} else {
await useDeleteData(deleteHost, data.id, 'commons.msg.delete');
await useDeleteData(deleteHost, data.id, 'commons.msg.delete', true);
}
loadHostTree();
loadGroups();

View File

@ -174,7 +174,7 @@
</template>
<script setup lang="ts">
import { onMounted, onBeforeMount, ref, watch, nextTick, reactive, getCurrentInstance } from 'vue';
import { onMounted, onBeforeMount, ref, watch, reactive, getCurrentInstance } from 'vue';
import { Rules } from '@/global/form-rues';
import { testConn, getHostTree, addHost } from '@/api/modules/host';
import { getCommandList } from '@/api/modules/command';
@ -396,13 +396,6 @@ const onConnLocal = () => {
terminalValue.value = `127.0.0.1-${tabIndex}`;
};
function changeFrameHeight() {
let ifm = document.getElementById('iframeTerminal') as HTMLInputElement | null;
if (ifm) {
ifm.style.height = document.documentElement.clientHeight - 300 + 'px';
}
}
function syncTerminal() {
for (const terminal of terminalTabs.value) {
if (ctx && ctx.refs[`Ref${terminal.key}`][0]) {
@ -413,10 +406,6 @@ function syncTerminal() {
onMounted(() => {
onConnLocal();
nextTick(() => {
changeFrameHeight();
window.addEventListener('resize', changeFrameHeight);
});
loadHost();
loadCommand();
timer = setInterval(() => {