feat: 新增linux进程操作

This commit is contained in:
meilin.huang
2022-01-16 21:45:00 +08:00
parent 1df943d825
commit 5cd9b61acb
11 changed files with 285 additions and 21 deletions

View File

@@ -51,8 +51,8 @@
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<slot name="btns" :submitDisabled="submitDisabled" :data="form" :submit="submit"> <slot name="btns" :submitDisabled="submitDisabled" :data="form" :submit="submit">
<el-button @click="reset" size="mini"> </el-button> <el-button @click="reset" size="small"> </el-button>
<el-button type="primary" @click="submit" size="mini"> </el-button> <el-button type="primary" @click="submit" size="small"> </el-button>
</slot> </slot>
</el-row> </el-row>
</el-form> </el-form>

View File

@@ -4,8 +4,8 @@
<dynamic-form ref="df" :form-info="formInfo" :form-data="formData" @submitSuccess="submitSuccess"> <dynamic-form ref="df" :form-info="formInfo" :form-data="formData" @submitSuccess="submitSuccess">
<template #btns="props"> <template #btns="props">
<slot name="btns"> <slot name="btns">
<el-button :disabled="props.submitDisabled" type="primary" @click="props.submit" size="mini"> </el-button> <el-button :disabled="props.submitDisabled" type="primary" @click="props.submit" size="small"> </el-button>
<el-button :disabled="props.submitDisabled" @click="close()" size="mini"> </el-button> <el-button :disabled="props.submitDisabled" @click="close()" size="small"> </el-button>
</slot> </slot>
</template> </template>
</dynamic-form> </dynamic-form>

View File

@@ -152,7 +152,7 @@
:min="0" :min="0"
:max="9999" :max="9999"
@change="setLocalThemeConfig" @change="setLocalThemeConfig"
size="mini" size="small"
style="width: 90px" style="width: 90px"
> >
</el-input-number> </el-input-number>
@@ -236,7 +236,7 @@
<div class="layout-breadcrumb-seting-bar-flex-value"> <div class="layout-breadcrumb-seting-bar-flex-value">
<el-input <el-input
v-model="getThemeConfig.wartermarkText" v-model="getThemeConfig.wartermarkText"
size="mini" size="small"
style="width: 90px" style="width: 90px"
@input="onWartermarkTextInput($event)" @input="onWartermarkTextInput($event)"
></el-input> ></el-input>
@@ -251,7 +251,7 @@
<el-select <el-select
v-model="getThemeConfig.tagsStyle" v-model="getThemeConfig.tagsStyle"
placeholder="请选择" placeholder="请选择"
size="mini" size="small"
style="width: 90px" style="width: 90px"
@change="setLocalThemeConfig" @change="setLocalThemeConfig"
> >
@@ -268,7 +268,7 @@
<el-select <el-select
v-model="getThemeConfig.animation" v-model="getThemeConfig.animation"
placeholder="请选择" placeholder="请选择"
size="mini" size="small"
style="width: 90px" style="width: 90px"
@change="setLocalThemeConfig" @change="setLocalThemeConfig"
> >
@@ -284,7 +284,7 @@
<el-select <el-select
v-model="getThemeConfig.columnsAsideStyle" v-model="getThemeConfig.columnsAsideStyle"
placeholder="请选择" placeholder="请选择"
size="mini" size="small"
style="width: 90px" style="width: 90px"
@change="setLocalThemeConfig" @change="setLocalThemeConfig"
> >

View File

@@ -171,7 +171,6 @@ import { codemirror } from '@/components/codemirror';
import { getSession } from '@/common/utils/storage'; import { getSession } from '@/common/utils/storage';
import enums from './enums'; import enums from './enums';
import config from '@/common/config'; import config from '@/common/config';
import SvgIcon from '@/components/svgIcon/index.vue';
export default defineComponent({ export default defineComponent({
name: 'FileManage', name: 'FileManage',

View File

@@ -32,8 +32,8 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini"> </el-button> <el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()" size="mini"> </el-button> <el-button @click="cancel()"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -53,17 +53,12 @@
{{ $filters.dateFormat(scope.row.createTime) }} {{ $filters.dateFormat(scope.row.createTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="creator" label="创建者" min-width="55"></el-table-column> <el-table-column prop="creator" label="创建者" min-width="60"></el-table-column>
<!-- <el-table-column prop="updateTime" label="更新时间" min-width="160">
<template #default="scope">
{{ $filters.dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" :min-width="55"></el-table-column> -->
<el-table-column label="操作" min-width="260" fixed="right"> <el-table-column label="操作" min-width="260" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button type="success" @click="serviceManager(scope.row)" plain size="small">脚本</el-button> <el-button type="success" @click="serviceManager(scope.row)" plain size="small">脚本</el-button>
<el-button v-auth="'machine:terminal'" type="primary" @click="showTerminal(scope.row)" plain size="small">终端</el-button> <el-button v-auth="'machine:terminal'" type="primary" @click="showTerminal(scope.row)" plain size="small">终端</el-button>
<el-button @click="showProcess(scope.row)" plain size="small">进程</el-button>
<el-button :disabled="!scope.row.hasCli" type="danger" @click="closeCli(scope.row)" plain size="small">关闭连接</el-button> <el-button :disabled="!scope.row.hasCli" type="danger" @click="closeCli(scope.row)" plain size="small">关闭连接</el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -91,6 +86,8 @@
<monitor ref="monitorDialogRef" :machineId="monitorDialog.machineId" /> <monitor ref="monitorDialogRef" :machineId="monitorDialog.machineId" />
</el-dialog> --> </el-dialog> -->
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" /> <service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" /> <file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
@@ -107,11 +104,13 @@ import { projectApi } from '../project/api.ts';
import ServiceManage from './ServiceManage.vue'; import ServiceManage from './ServiceManage.vue';
import FileManage from './FileManage.vue'; import FileManage from './FileManage.vue';
import MachineEdit from './MachineEdit.vue'; import MachineEdit from './MachineEdit.vue';
import ProcessList from './ProcessList.vue';
export default defineComponent({ export default defineComponent({
name: 'MachineList', name: 'MachineList',
components: { components: {
ServiceManage, ServiceManage,
ProcessList,
FileManage, FileManage,
MachineEdit, MachineEdit,
}, },
@@ -142,6 +141,10 @@ export default defineComponent({
machineId: 0, machineId: 0,
title: '', title: '',
}, },
processDialog: {
visible: false,
machineId: 0,
},
fileDialog: { fileDialog: {
visible: false, visible: false,
machineId: 0, machineId: 0,
@@ -256,6 +259,11 @@ export default defineComponent({
state.data = res; state.data = res;
}; };
const showProcess = (row: any) => {
state.processDialog.machineId = row.id;
state.processDialog.visible = true;
};
return { return {
...toRefs(state), ...toRefs(state),
choose, choose,
@@ -266,6 +274,7 @@ export default defineComponent({
deleteMachine, deleteMachine,
closeCli, closeCli,
serviceManager, serviceManager,
showProcess,
submitSuccess, submitSuccess,
fileManage, fileManage,
search, search,

View File

@@ -0,0 +1,203 @@
<template>
<div class="file-manage">
<el-dialog title="进程信息" v-model="dialogVisible" :destroy-on-close="true" :show-close="true" :before-close="handleClose" width="65%">
<div class="toolbar">
<el-row>
<el-col :span="4">
<el-input size="small" placeholder="进程名" v-model="params.name" plain clearable></el-input>
</el-col>
<el-col :span="4" class="ml5">
<el-select @change="getProcess" size="small" v-model="params.sortType" placeholder="请选择排序类型">
<el-option key="cpu" label="cpu降序" value="1"> </el-option>
<el-option key="cpu" label="mem降序" value="2"> </el-option>
</el-select>
</el-col>
<el-col :span="4" class="ml5">
<el-select @change="getProcess" size="small" v-model="params.count" placeholder="请选择进程个数">
<el-option key="10" label="10" value="10"> </el-option>
<el-option key="15" label="15" value="15"> </el-option>
<el-option key="20" label="20" value="20"> </el-option>
<el-option key="25" label="25" value="25"> </el-option>
</el-select>
</el-col>
<el-col :span="6">
<el-button class="ml5" @click="getProcess" type="primary" icon="tickets" size="small" plain>刷新</el-button>
</el-col>
</el-row>
</div>
<el-table :data="processList" size="small" style="width: 100%" empty-text="获取进程中...">
<el-table-column prop="user" label="USER" :min-width="50"> </el-table-column>
<el-table-column prop="pid" label="PID" :min-width="50" show-overflow-tooltip></el-table-column>
<el-table-column prop="cpu" label="%CPU" :min-width="40"> </el-table-column>
<el-table-column prop="mem" label="%MEM" :min-width="42"> </el-table-column>
<el-table-column prop="vsz" label="vsz" :min-width="55">
<template #header>
VSZ
<el-tooltip class="box-item" effect="dark" content="虚拟内存" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="rss" :min-width="52">
<template #header>
RSS
<el-tooltip class="box-item" effect="dark" content="固定内存" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="stat" :min-width="50">
<template #header>
STAT
<el-tooltip class="box-item" effect="dark" content="进程状态" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="start" :min-width="50">
<template #header>
START
<el-tooltip class="box-item" effect="dark" content="启动时间" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="time" :min-width="50">
<template #header>
TIME
<el-tooltip class="box-item" effect="dark" content="该进程实际使用CPU运作的时间" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="command" label="command" :min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-popconfirm title="确定终止该进程?" @confirm="confirmKillProcess(scope.row.pid)">
<template #reference>
<el-button v-auth="'machine:killprocess'" type="danger" icon="delete" size="small" plain>终止</el-button>
</template>
</el-popconfirm>
<!-- <el-button @click="addFiles(scope.row)" type="danger" icon="delete" size="small" plain>终止</el-button> -->
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api';
import enums from './enums';
export default defineComponent({
name: 'ProcessList',
components: {},
props: {
visible: { type: Boolean },
machineId: { type: Number },
title: { type: String },
},
setup(props: any, context) {
const state = reactive({
dialogVisible: false,
params: {
name: '',
sortType: '1',
count: '10',
id: 0,
},
processList: [],
});
watch(props, (newValue) => {
if (props.machineId) {
state.params.id = props.machineId;
getProcess();
}
state.dialogVisible = newValue.visible;
});
const getProcess = async () => {
const res = await machineApi.process.request(state.params);
// 解析字符串
// USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
// root 1 0.0 0.0 125632 3352 ? Ss 2019 154:04 /usr/lib/systemd/systemd --system --deserialize 22
const psStrings = res.split('\n');
const ps = [];
for (let i = 1; i < psStrings.length; i++) {
const psStr = psStrings[i];
const process = psStr.split(/\s+/);
if (process.length < 2) {
continue;
}
let command = process[10];
// 搜索进程时由于使用grep命令可能会多个bash或grep进程
if (state.params.name) {
if (command == 'bash' || command == 'grep') {
continue;
}
}
// 获取command由于command中也有可能存在空格被切割故重新拼接
for (let j = 10; j < process.length - 1; j++) {
command += ' ' + process[j + 1];
}
ps.push({
user: process[0],
pid: process[1],
cpu: process[2],
mem: process[3],
vsz: kb2Mb(process[4]),
rss: kb2Mb(process[5]),
stat: process[7],
start: process[8],
time: process[9],
command,
});
}
state.processList = ps as any;
};
const confirmKillProcess = async (pid: any) => {
await machineApi.killProcess.request({
pid,
id: state.params.id,
});
ElMessage.success('kill success');
state.params.name = '';
getProcess();
};
const kb2Mb = (kb: string) => {
return (parseInt(kb) / 1024).toFixed(2) + 'M';
};
/**
* 关闭取消按钮触发的事件
*/
const handleClose = () => {
context.emit('update:visible', false);
context.emit('update:machineId', null);
context.emit('cancel');
state.params = {
name: '',
sortType: '1',
count: '10',
id: 0,
};
state.processList = [];
};
return {
...toRefs(state),
getProcess,
confirmKillProcess,
enums,
handleClose,
};
},
});
</script>

View File

@@ -40,11 +40,11 @@
type="primary" type="primary"
:loading="btnLoading" :loading="btnLoading"
@click="btnOk" @click="btnOk"
size="mini" size="small"
:disabled="submitDisabled" :disabled="submitDisabled"
> </el-button > </el-button
> >
<el-button @click="cancel()" :disabled="submitDisabled" size="mini"> </el-button> <el-button @click="cancel()" :disabled="submitDisabled" size="small"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -5,6 +5,9 @@ export const machineApi = {
list: Api.create("/machines", 'get'), list: Api.create("/machines", 'get'),
info: Api.create("/machines/{id}/sysinfo", 'get'), info: Api.create("/machines/{id}/sysinfo", 'get'),
stats: Api.create("/machines/{id}/stats", 'get'), stats: Api.create("/machines/{id}/stats", 'get'),
process: Api.create("/machines/{id}/process", 'get'),
// 终止进程
killProcess: Api.create("/machines/{id}/process", 'delete'),
closeCli: Api.create("/machines/{id}/close-cli", 'delete'), closeCli: Api.create("/machines/{id}/close-cli", 'delete'),
// 保存按钮 // 保存按钮
saveMachine: Api.create("/machines", 'post'), saveMachine: Api.create("/machines", 'post'),

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"bytes" "bytes"
"fmt"
"mayfly-go/base/biz" "mayfly-go/base/biz"
"mayfly-go/base/ctx" "mayfly-go/base/ctx"
"mayfly-go/base/ginx" "mayfly-go/base/ginx"
@@ -72,6 +73,41 @@ func (m *Machine) CloseCli(rc *ctx.ReqCtx) {
machine.DeleteCli(GetMachineId(rc.GinCtx)) machine.DeleteCli(GetMachineId(rc.GinCtx))
} }
// 获取进程列表信息
func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
g := rc.GinCtx
cmd := "ps -aux "
sortType := g.Query("sortType")
if sortType == "2" {
cmd += "--sort -pmem "
} else {
cmd += "--sort -pcpu "
}
pname := g.Query("name")
if pname != "" {
cmd += fmt.Sprintf("| grep %s ", pname)
}
count := g.Query("count")
if count == "" {
count = "10"
}
cmd += "| head -n " + count
res, err := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
rc.ResData = res
}
// 终止进程
func (m *Machine) KillProcess(rc *ctx.ReqCtx) {
pid := rc.GinCtx.Query("pid")
biz.NotEmpty(pid, "进程id不能为空")
_, err := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).Run("kill -9 " + pid)
biz.ErrIsNilAppendErr(err, "终止进程失败: %s")
}
func (m *Machine) WsSSH(g *gin.Context) { func (m *Machine) WsSSH(g *gin.Context) {
wsConn, err := ws.Upgrader.Upgrade(g.Writer, g.Request, nil) wsConn, err := ws.Upgrader.Upgrade(g.Writer, g.Request, nil)
defer func() { defer func() {

View File

@@ -20,6 +20,20 @@ func InitMachineRouter(router *gin.RouterGroup) {
ctx.NewReqCtxWithGin(c).Handle(m.MachineStats) ctx.NewReqCtxWithGin(c).Handle(m.MachineStats)
}) })
machines.GET(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProcess)
})
// 终止进程
killProcessL := ctx.NewLogInfo("终止进程")
killProcessP := ctx.NewPermission("machine:killprocess")
machines.DELETE(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(killProcessL).
WithRequiredPermission(killProcessP).
Handle(m.KillProcess)
})
saveMachine := ctx.NewLogInfo("保存机器信息") saveMachine := ctx.NewLogInfo("保存机器信息")
machines.POST("", func(c *gin.Context) { machines.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c). ctx.NewReqCtxWithGin(c).