mirror of https://github.com/1Panel-dev/1Panel
zhengkunwang223
1 year ago
committed by
GitHub
24 changed files with 1652 additions and 619 deletions
@ -0,0 +1,40 @@
|
||||
package v1 |
||||
|
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" |
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request" |
||||
"github.com/1Panel-dev/1Panel/backend/constant" |
||||
websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket" |
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
func (b *BaseApi) ProcessWs(c *gin.Context) { |
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) |
||||
if err != nil { |
||||
return |
||||
} |
||||
wsClient := websocket2.NewWsClient("processClient", ws) |
||||
go wsClient.Read() |
||||
go wsClient.Write() |
||||
} |
||||
|
||||
// @Tags Process
|
||||
// @Summary Stop Process
|
||||
// @Description 停止进程
|
||||
// @Param request body request.ProcessReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /process/stop [post]
|
||||
// @x-panel-log {"bodyKeys":["PID"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"结束进程 [PID]","formatEN":"结束进程 [PID]"}
|
||||
func (b *BaseApi) StopProcess(c *gin.Context) { |
||||
var req request.ProcessReq |
||||
if err := c.ShouldBindJSON(&req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
if err := processService.StopProcess(req); err != nil { |
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |
||||
return |
||||
} |
||||
helper.SuccessWithOutData(c) |
||||
} |
@ -0,0 +1,5 @@
|
||||
package request |
||||
|
||||
type ProcessReq struct { |
||||
PID int32 `json:"PID" validate:"required"` |
||||
} |
@ -0,0 +1,27 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request" |
||||
"github.com/shirou/gopsutil/v3/process" |
||||
) |
||||
|
||||
type ProcessService struct{} |
||||
|
||||
type IProcessService interface { |
||||
StopProcess(req request.ProcessReq) error |
||||
} |
||||
|
||||
func NewIProcessService() IProcessService { |
||||
return &ProcessService{} |
||||
} |
||||
|
||||
func (p *ProcessService) StopProcess(req request.ProcessReq) error { |
||||
proc, err := process.NewProcess(req.PID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := proc.Kill(); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,20 @@
|
||||
package router |
||||
|
||||
import ( |
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1" |
||||
"github.com/1Panel-dev/1Panel/backend/middleware" |
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
type ProcessRouter struct { |
||||
} |
||||
|
||||
func (f *ProcessRouter) InitProcessRouter(Router *gin.RouterGroup) { |
||||
processRouter := Router.Group("process") |
||||
processRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired()) |
||||
baseApi := v1.ApiGroupApp.BaseApi |
||||
{ |
||||
processRouter.GET("/ws", baseApi.ProcessWs) |
||||
processRouter.POST("/stop", baseApi.StopProcess) |
||||
} |
||||
} |
@ -0,0 +1,68 @@
|
||||
package ps |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/shirou/gopsutil/v3/process" |
||||
"strconv" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestPs(t *testing.T) { |
||||
processes, err := process.Processes() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, pro := range processes { |
||||
var ( |
||||
name string |
||||
parentID int32 |
||||
userName string |
||||
status string |
||||
startTime string |
||||
numThreads int32 |
||||
numConnections int |
||||
cpuPercent float64 |
||||
//mem string
|
||||
rss string |
||||
ioRead string |
||||
ioWrite string |
||||
) |
||||
name, _ = pro.Name() |
||||
parentID, _ = pro.Ppid() |
||||
userName, _ = pro.Username() |
||||
array, err := pro.Status() |
||||
if err == nil { |
||||
status = array[0] |
||||
} |
||||
createTime, err := pro.CreateTime() |
||||
if err == nil { |
||||
t := time.Unix(createTime/1000, 0) |
||||
startTime = t.Format("2006-1-2 15:04:05") |
||||
} |
||||
numThreads, _ = pro.NumThreads() |
||||
connections, err := pro.Connections() |
||||
if err == nil && len(connections) > 0 { |
||||
numConnections = len(connections) |
||||
} |
||||
cpuPercent, _ = pro.CPUPercent() |
||||
menInfo, err := pro.MemoryInfo() |
||||
if err == nil { |
||||
rssF := float64(menInfo.RSS) / 1048576 |
||||
rss = fmt.Sprintf("%.2f", rssF) |
||||
} |
||||
ioStat, err := pro.IOCounters() |
||||
if err == nil { |
||||
ioWrite = strconv.FormatUint(ioStat.WriteBytes, 10) |
||||
ioRead = strconv.FormatUint(ioStat.ReadBytes, 10) |
||||
} |
||||
|
||||
cmdLine, err := pro.Cmdline() |
||||
if err == nil { |
||||
fmt.Println(cmdLine) |
||||
} |
||||
|
||||
fmt.Println(fmt.Sprintf("Name: %s PId: %v ParentID: %v Username: %v status:%s startTime: %s numThreads: %v numConnections:%v cpuPercent:%v rss:%s MB IORead: %s IOWrite: %s", |
||||
name, pro.Pid, parentID, userName, status, startTime, numThreads, numConnections, cpuPercent, rss, ioRead, ioWrite)) |
||||
} |
||||
} |
@ -1,38 +0,0 @@
|
||||
package websocket |
||||
|
||||
import "sync" |
||||
|
||||
type Manager struct { |
||||
Group map[string]*Client |
||||
Lock sync.Mutex |
||||
Register, UnRegister chan *Client |
||||
ClientCount uint |
||||
} |
||||
|
||||
func (m *Manager) Start() { |
||||
for { |
||||
select { |
||||
case client := <-m.Register: |
||||
m.Lock.Lock() |
||||
m.Group[client.ID] = client |
||||
m.ClientCount++ |
||||
m.Lock.Unlock() |
||||
case client := <-m.UnRegister: |
||||
m.Lock.Lock() |
||||
if _, ok := m.Group[client.ID]; ok { |
||||
close(client.Msg) |
||||
delete(m.Group, client.ID) |
||||
m.ClientCount-- |
||||
} |
||||
m.Lock.Unlock() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (m *Manager) RegisterClient(client *Client) { |
||||
m.Register <- client |
||||
} |
||||
|
||||
func (m *Manager) UnRegisterClient(client *Client) { |
||||
m.UnRegister <- client |
||||
} |
@ -0,0 +1,222 @@
|
||||
package websocket |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
"github.com/1Panel-dev/1Panel/backend/utils/files" |
||||
"github.com/shirou/gopsutil/v3/net" |
||||
"github.com/shirou/gopsutil/v3/process" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
type WsInput struct { |
||||
Type string `json:"type"` |
||||
DownloadProgress |
||||
PsProcessConfig |
||||
} |
||||
|
||||
type DownloadProgress struct { |
||||
Keys []string `json:"keys"` |
||||
} |
||||
|
||||
type PsProcessConfig struct { |
||||
Pid int32 `json:"pid"` |
||||
Name string `json:"name"` |
||||
Username string `json:"username"` |
||||
} |
||||
|
||||
type PsProcessData struct { |
||||
PID int32 `json:"PID"` |
||||
Name string `json:"name"` |
||||
PPID int32 `json:"PPID"` |
||||
Username string `json:"username"` |
||||
Status string `json:"status"` |
||||
StartTime string `json:"startTime"` |
||||
NumThreads int32 `json:"numThreads"` |
||||
NumConnections int `json:"numConnections"` |
||||
CpuPercent string `json:"cpuPercent"` |
||||
|
||||
DiskRead string `json:"diskRead"` |
||||
DiskWrite string `json:"diskWrite"` |
||||
CmdLine string `json:"cmdLine"` |
||||
|
||||
Rss string `json:"rss"` |
||||
VMS string `json:"vms"` |
||||
HWM string `json:"hwm"` |
||||
Data string `json:"data"` |
||||
Stack string `json:"stack"` |
||||
Locked string `json:"locked"` |
||||
Swap string `json:"swap"` |
||||
|
||||
CpuValue float64 `json:"cpuValue"` |
||||
RssValue uint64 `json:"rssValue"` |
||||
|
||||
Envs []string `json:"envs"` |
||||
|
||||
OpenFiles []process.OpenFilesStat `json:"openFiles"` |
||||
Connects []processConnect `json:"connects"` |
||||
} |
||||
|
||||
type processConnect struct { |
||||
Type string `json:"type"` |
||||
Status string `json:"status"` |
||||
Laddr net.Addr `json:"localaddr"` |
||||
Raddr net.Addr `json:"remoteaddr"` |
||||
} |
||||
|
||||
func ProcessData(c *Client, inputMsg []byte) { |
||||
wsInput := &WsInput{} |
||||
err := json.Unmarshal(inputMsg, wsInput) |
||||
if err != nil { |
||||
global.LOG.Errorf("unmarshal wsInput error,err %s", err.Error()) |
||||
return |
||||
} |
||||
switch wsInput.Type { |
||||
case "wget": |
||||
res, err := getDownloadProcess(wsInput.DownloadProgress) |
||||
if err != nil { |
||||
return |
||||
} |
||||
c.Msg <- res |
||||
case "ps": |
||||
res, err := getProcessData(wsInput.PsProcessConfig) |
||||
if err != nil { |
||||
return |
||||
} |
||||
c.Msg <- res |
||||
} |
||||
|
||||
} |
||||
|
||||
func getDownloadProcess(progress DownloadProgress) (res []byte, err error) { |
||||
var result []files.Process |
||||
for _, k := range progress.Keys { |
||||
value, err := global.CACHE.Get(k) |
||||
if err != nil { |
||||
global.LOG.Errorf("get cache error,err %s", err.Error()) |
||||
return nil, err |
||||
} |
||||
downloadProcess := &files.Process{} |
||||
_ = json.Unmarshal(value, downloadProcess) |
||||
result = append(result, *downloadProcess) |
||||
} |
||||
res, err = json.Marshal(result) |
||||
return |
||||
} |
||||
|
||||
const ( |
||||
b = uint64(1) |
||||
kb = 1024 * b |
||||
mb = 1024 * kb |
||||
gb = 1024 * mb |
||||
) |
||||
|
||||
func formatBytes(bytes uint64) string { |
||||
switch { |
||||
case bytes < kb: |
||||
return fmt.Sprintf("%dB", bytes) |
||||
case bytes < mb: |
||||
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb)) |
||||
case bytes < gb: |
||||
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb)) |
||||
default: |
||||
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb)) |
||||
} |
||||
} |
||||
|
||||
func getProcessData(processConfig PsProcessConfig) (res []byte, err error) { |
||||
var ( |
||||
result []PsProcessData |
||||
processes []*process.Process |
||||
) |
||||
processes, err = process.Processes() |
||||
if err != nil { |
||||
return |
||||
} |
||||
for _, proc := range processes { |
||||
procData := PsProcessData{ |
||||
PID: proc.Pid, |
||||
} |
||||
if processConfig.Pid > 0 && processConfig.Pid != proc.Pid { |
||||
continue |
||||
} |
||||
if procName, err := proc.Name(); err == nil { |
||||
procData.Name = procName |
||||
} else { |
||||
procData.Name = "<UNKNOWN>" |
||||
} |
||||
if processConfig.Name != "" && !strings.Contains(procData.Name, processConfig.Name) { |
||||
continue |
||||
} |
||||
if username, err := proc.Username(); err == nil { |
||||
procData.Username = username |
||||
} |
||||
if processConfig.Username != "" && !strings.Contains(procData.Username, processConfig.Username) { |
||||
continue |
||||
} |
||||
procData.PPID, _ = proc.Ppid() |
||||
statusArray, _ := proc.Status() |
||||
if len(statusArray) > 0 { |
||||
procData.Status = strings.Join(statusArray, ",") |
||||
} |
||||
createTime, procErr := proc.CreateTime() |
||||
if procErr == nil { |
||||
t := time.Unix(createTime/1000, 0) |
||||
procData.StartTime = t.Format("2006-1-2 15:04:05") |
||||
} |
||||
procData.NumThreads, _ = proc.NumThreads() |
||||
connections, procErr := proc.Connections() |
||||
if procErr == nil { |
||||
procData.NumConnections = len(connections) |
||||
for _, conn := range connections { |
||||
if conn.Laddr.IP != "" || conn.Raddr.IP != "" { |
||||
procData.Connects = append(procData.Connects, processConnect{ |
||||
Status: conn.Status, |
||||
Laddr: conn.Laddr, |
||||
Raddr: conn.Raddr, |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
procData.CpuValue, _ = proc.CPUPercent() |
||||
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%" |
||||
menInfo, procErr := proc.MemoryInfo() |
||||
if procErr == nil { |
||||
procData.Rss = formatBytes(menInfo.RSS) |
||||
procData.RssValue = menInfo.RSS |
||||
procData.Data = formatBytes(menInfo.Data) |
||||
procData.VMS = formatBytes(menInfo.VMS) |
||||
procData.HWM = formatBytes(menInfo.HWM) |
||||
procData.Stack = formatBytes(menInfo.Stack) |
||||
procData.Locked = formatBytes(menInfo.Locked) |
||||
procData.Swap = formatBytes(menInfo.Swap) |
||||
} else { |
||||
procData.Rss = "--" |
||||
procData.Data = "--" |
||||
procData.VMS = "--" |
||||
procData.HWM = "--" |
||||
procData.Stack = "--" |
||||
procData.Locked = "--" |
||||
procData.Swap = "--" |
||||
|
||||
procData.RssValue = 0 |
||||
} |
||||
ioStat, procErr := proc.IOCounters() |
||||
if procErr == nil { |
||||
procData.DiskWrite = formatBytes(ioStat.WriteBytes) |
||||
procData.DiskRead = formatBytes(ioStat.ReadBytes) |
||||
} else { |
||||
procData.DiskWrite = "--" |
||||
procData.DiskRead = "--" |
||||
} |
||||
procData.CmdLine, _ = proc.Cmdline() |
||||
procData.OpenFiles, _ = proc.OpenFiles() |
||||
procData.Envs, _ = proc.Environ() |
||||
|
||||
result = append(result, procData) |
||||
} |
||||
res, err = json.Marshal(result) |
||||
return |
||||
} |
@ -0,0 +1,5 @@
|
||||
export namespace Process { |
||||
export interface StopReq { |
||||
PID: number; |
||||
} |
||||
} |
@ -0,0 +1,6 @@
|
||||
import http from '@/api'; |
||||
import { Process } from '../interface/process'; |
||||
|
||||
export const StopProcess = (req: Process.StopReq) => { |
||||
return http.post<any>(`/process/stop`, req); |
||||
}; |
@ -0,0 +1,20 @@
|
||||
<template> |
||||
<div> |
||||
<RouterButton :buttons="buttons" /> |
||||
<LayoutContent> |
||||
<router-view></router-view> |
||||
</LayoutContent> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import i18n from '@/lang'; |
||||
import RouterButton from '@/components/router-button/index.vue'; |
||||
|
||||
const buttons = [ |
||||
{ |
||||
label: i18n.global.t('menu.process'), |
||||
path: '/hosts/process/process', |
||||
}, |
||||
]; |
||||
</script> |
@ -0,0 +1,129 @@
|
||||
<template> |
||||
<el-drawer v-model="open" size="40%"> |
||||
<template #header> |
||||
<DrawerHeader :header="$t('app.detail')" :back="handleClose" :resource="resourceName" /> |
||||
</template> |
||||
<el-row> |
||||
<el-col> |
||||
<el-tabs v-model="activeName" type="card"> |
||||
<el-tab-pane :label="$t('process.basic')" name="basic"> |
||||
<el-descriptions :column="2" border> |
||||
<el-descriptions-item :label="$t('commons.table.name')" min-width="100px"> |
||||
{{ data.name }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.status')">{{ data.status }}</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.pid')">{{ data.PID }}</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.ppid')">{{ data.PPID }}</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.numThreads')"> |
||||
{{ data.numThreads }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.numConnections')"> |
||||
{{ data.numConnections }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.diskRead')"> |
||||
{{ data.diskRead }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.diskWrite')"> |
||||
{{ data.diskWrite }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.username')"> |
||||
{{ data.username }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.startTime')"> |
||||
{{ data.startTime }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('process.cmdLine')"> |
||||
{{ data.cmdLine }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</el-tab-pane> |
||||
<el-tab-pane :label="$t('process.mem')" name="mem"> |
||||
<el-descriptions :column="2" border> |
||||
<el-descriptions-item :label="'rss'">{{ data.rss }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'swap'">{{ data.swap }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'vms'">{{ data.vms }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'hwm'">{{ data.hwm }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'data'">{{ data.data }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'stack'">{{ data.stack }}</el-descriptions-item> |
||||
<el-descriptions-item :label="'locked'">{{ data.locked }}</el-descriptions-item> |
||||
</el-descriptions> |
||||
</el-tab-pane> |
||||
<el-tab-pane :label="$t('process.openFiles')" name="openFiles"> |
||||
<el-table :data="data.openFiles" border style="width: 100%"> |
||||
<el-table-column prop="path" :label="$t('process.file')" /> |
||||
<el-table-column prop="fd" label="fd" width="100px" /> |
||||
</el-table> |
||||
</el-tab-pane> |
||||
<el-tab-pane :label="$t('process.env')" name="env"> |
||||
<codemirror |
||||
:autofocus="true" |
||||
:indent-with-tab="true" |
||||
:tabSize="4" |
||||
style="height: calc(100vh - 200px)" |
||||
:lineWrapping="true" |
||||
:matchBrackets="true" |
||||
theme="cobalt" |
||||
:styleActiveLine="true" |
||||
:extensions="extensions" |
||||
v-model="envStr" |
||||
:disabled="true" |
||||
/> |
||||
</el-tab-pane> |
||||
<el-tab-pane :label="$t('process.net')" name="net"> |
||||
<el-table :data="data.connects" border style="width: 100%"> |
||||
<el-table-column prop="localaddr" :label="$t('process.laddr')"> |
||||
<template #default="{ row }"> |
||||
<span>{{ row.localaddr.ip }}</span> |
||||
<span v-if="row.localaddr.port > 0">:{{ row.localaddr.port }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="remoteaddr" :label="$t('process.raddr')"> |
||||
<template #default="{ row }"> |
||||
<span>{{ row.remoteaddr.ip }}</span> |
||||
<span v-if="row.remoteaddr.port > 0">:{{ row.remoteaddr.port }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="status" :label="$t('app.status')" /> |
||||
</el-table> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
</el-col> |
||||
</el-row> |
||||
</el-drawer> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue'; |
||||
import DrawerHeader from '@/components/drawer-header/index.vue'; |
||||
import { Codemirror } from 'vue-codemirror'; |
||||
import { javascript } from '@codemirror/lang-javascript'; |
||||
import { oneDark } from '@codemirror/theme-one-dark'; |
||||
|
||||
interface InfoProps { |
||||
info: object; |
||||
} |
||||
|
||||
let open = ref(false); |
||||
let data = ref(); |
||||
const resourceName = ref(''); |
||||
const activeName = ref('basic'); |
||||
const envStr = ref(''); |
||||
|
||||
const extensions = [javascript(), oneDark]; |
||||
|
||||
const handleClose = () => { |
||||
open.value = false; |
||||
}; |
||||
|
||||
const acceptParams = async (params: InfoProps): Promise<void> => { |
||||
activeName.value = 'basic'; |
||||
data.value = params.info; |
||||
resourceName.value = data.value.name; |
||||
envStr.value = data.value.envs.join('\n'); |
||||
open.value = true; |
||||
}; |
||||
|
||||
defineExpose({ |
||||
acceptParams, |
||||
}); |
||||
</script> |
@ -0,0 +1,284 @@
|
||||
<template> |
||||
<div> |
||||
<FireRouter /> |
||||
<LayoutContent :title="$t('menu.process')" v-loading="loading"> |
||||
<template #toolbar> |
||||
<el-row> |
||||
<el-col :span="24"> |
||||
<div style="width: 100%"> |
||||
<el-form-item style="float: right"> |
||||
<el-row :gutter="20"> |
||||
<el-col :span="8"> |
||||
<div class="search-button"> |
||||
<el-input |
||||
typpe="number" |
||||
v-model.number="processSearch.pid" |
||||
clearable |
||||
@clear="search()" |
||||
suffix-icon="Search" |
||||
@keyup.enter="search()" |
||||
@change="search()" |
||||
:placeholder="$t('process.pid')" |
||||
></el-input> |
||||
</div> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<div class="search-button"> |
||||
<el-input |
||||
v-model.trim="processSearch.name" |
||||
clearable |
||||
@clear="search()" |
||||
suffix-icon="Search" |
||||
@keyup.enter="search()" |
||||
@change="search()" |
||||
:placeholder="$t('commons.table.name')" |
||||
></el-input> |
||||
</div> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<div class="search-button"> |
||||
<el-input |
||||
v-model.trim="processSearch.username" |
||||
clearable |
||||
@clear="search()" |
||||
suffix-icon="Search" |
||||
@keyup.enter="search()" |
||||
@change="search()" |
||||
:placeholder="$t('process.username')" |
||||
></el-input> |
||||
</div> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form-item> |
||||
</div> |
||||
</el-col> |
||||
</el-row> |
||||
</template> |
||||
<template #main> |
||||
<ComplexTable :data="data" @sort-change="changeSort" @filter-change="changeFilter" ref="tableRef"> |
||||
<el-table-column :label="'PID'" fix prop="PID" max-width="60px" sortable> |
||||
<template #default="{ row }"> |
||||
<el-link @click="openDetail(row)">{{ row.PID }}</el-link> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
:label="$t('commons.table.name')" |
||||
fix |
||||
prop="name" |
||||
min-width="120px" |
||||
></el-table-column> |
||||
<el-table-column :label="$t('process.ppid')" fix prop="PPID" sortable></el-table-column> |
||||
<el-table-column :label="$t('process.numThreads')" fix prop="numThreads"></el-table-column> |
||||
<el-table-column :label="$t('process.username')" fix prop="username"></el-table-column> |
||||
<el-table-column |
||||
:label="'CPU'" |
||||
fix |
||||
prop="cpuValue" |
||||
:formatter="cpuFormatter" |
||||
sortable |
||||
></el-table-column> |
||||
<el-table-column |
||||
:label="$t('process.memory')" |
||||
fix |
||||
prop="rssValue" |
||||
:formatter="memFormatter" |
||||
sortable |
||||
></el-table-column> |
||||
<el-table-column :label="$t('process.numConnections')" fix prop="numConnections"></el-table-column> |
||||
<el-table-column |
||||
:label="$t('process.status')" |
||||
fix |
||||
prop="status" |
||||
column-key="status" |
||||
:filters="[ |
||||
{ text: $t('process.running'), value: 'running' }, |
||||
{ text: $t('process.sleep'), value: 'sleep' }, |
||||
{ text: $t('process.stop'), value: 'stop' }, |
||||
{ text: $t('process.idle'), value: 'idle' }, |
||||
{ text: $t('process.wait'), value: 'wait' }, |
||||
{ text: $t('process.lock'), value: 'lock' }, |
||||
{ text: $t('process.zombie'), value: 'zombie' }, |
||||
]" |
||||
:filter-method="filterStatus" |
||||
:filtered-value="sortConfig.filters" |
||||
> |
||||
<template #default="{ row }"> |
||||
<span v-if="row.status">{{ $t('process.' + row.status) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
:label="$t('process.startTime')" |
||||
fix |
||||
prop="startTime" |
||||
min-width="120px" |
||||
></el-table-column> |
||||
<fu-table-operations :ellipsis="10" :buttons="buttons" :label="$t('commons.table.operate')" fix /> |
||||
</ComplexTable> |
||||
</template> |
||||
</LayoutContent> |
||||
<ProcessDetail ref="detailRef" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import FireRouter from '@/views/host/process/index.vue'; |
||||
import { ref, onMounted, onUnmounted, nextTick, reactive } from 'vue'; |
||||
import ProcessDetail from './detail/index.vue'; |
||||
import i18n from '@/lang'; |
||||
import { StopProcess } from '@/api/modules/process'; |
||||
import { useDeleteData } from '@/hooks/use-delete-data'; |
||||
|
||||
interface SortStatus { |
||||
prop: ''; |
||||
order: ''; |
||||
filters: []; |
||||
} |
||||
const sortConfig: SortStatus = { |
||||
prop: '', |
||||
order: '', |
||||
filters: [], |
||||
}; |
||||
|
||||
const processSearch = reactive({ |
||||
type: 'ps', |
||||
pid: undefined, |
||||
username: '', |
||||
name: '', |
||||
}); |
||||
|
||||
const buttons = [ |
||||
{ |
||||
label: i18n.global.t('app.detail'), |
||||
click: function (row: any) { |
||||
openDetail(row); |
||||
}, |
||||
}, |
||||
{ |
||||
label: i18n.global.t('process.stopProcess'), |
||||
click: function (row: any) { |
||||
stopProcess(row.PID); |
||||
}, |
||||
}, |
||||
]; |
||||
|
||||
let processSocket = ref(null) as unknown as WebSocket; |
||||
const data = ref([]); |
||||
const loading = ref(false); |
||||
const tableRef = ref(); |
||||
const oldData = ref([]); |
||||
const detailRef = ref(); |
||||
|
||||
const openDetail = (row: any) => { |
||||
detailRef.value.acceptParams({ info: row }); |
||||
}; |
||||
|
||||
const changeSort = ({ prop, order }) => { |
||||
sortConfig.prop = prop; |
||||
sortConfig.order = order; |
||||
}; |
||||
|
||||
const changeFilter = (filters: any) => { |
||||
if (filters.status && filters.status.length > 0) { |
||||
sortConfig.filters = filters.status; |
||||
data.value = filterByStatus(); |
||||
sortTable(); |
||||
} else { |
||||
data.value = oldData.value; |
||||
sortConfig.filters = []; |
||||
sortTable(); |
||||
} |
||||
}; |
||||
|
||||
const filterStatus = (value: string, row: any) => { |
||||
return row.status === value; |
||||
}; |
||||
|
||||
const cpuFormatter = (row: any) => { |
||||
return row.cpuPercent; |
||||
}; |
||||
|
||||
const memFormatter = (row: any) => { |
||||
return row.rss; |
||||
}; |
||||
|
||||
const isWsOpen = () => { |
||||
const readyState = processSocket && processSocket.readyState; |
||||
return readyState === 1; |
||||
}; |
||||
const closeSocket = () => { |
||||
if (isWsOpen()) { |
||||
processSocket && processSocket.close(); |
||||
} |
||||
}; |
||||
|
||||
const onOpenProcess = () => {}; |
||||
const onMessage = (message: any) => { |
||||
let result: any[] = JSON.parse(message.data); |
||||
oldData.value = result; |
||||
data.value = filterByStatus(); |
||||
sortTable(); |
||||
}; |
||||
|
||||
const filterByStatus = () => { |
||||
if (sortConfig.filters.length > 0) { |
||||
const newData = oldData.value.filter((re: any) => { |
||||
return (sortConfig.filters as string[]).indexOf(re.status) > -1; |
||||
}); |
||||
return newData; |
||||
} else { |
||||
return oldData.value; |
||||
} |
||||
}; |
||||
|
||||
const sortTable = () => { |
||||
if (sortConfig.prop != '' && sortConfig.order != '') { |
||||
nextTick(() => { |
||||
tableRef.value?.sort(sortConfig.prop, sortConfig.order); |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
const onerror = () => {}; |
||||
const onClose = () => {}; |
||||
|
||||
const initProcess = () => { |
||||
let href = window.location.href; |
||||
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss'; |
||||
let ipLocal = href.split('//')[1].split('/')[0]; |
||||
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/process/ws`); |
||||
processSocket.onopen = onOpenProcess; |
||||
processSocket.onmessage = onMessage; |
||||
processSocket.onerror = onerror; |
||||
processSocket.onclose = onClose; |
||||
sendMsg(); |
||||
}; |
||||
|
||||
const sendMsg = () => { |
||||
setInterval(() => { |
||||
search(); |
||||
}, 3000); |
||||
}; |
||||
|
||||
const search = () => { |
||||
if (isWsOpen()) { |
||||
if (typeof processSearch.pid === 'string') { |
||||
processSearch.pid = undefined; |
||||
} |
||||
processSocket.send(JSON.stringify(processSearch)); |
||||
} |
||||
}; |
||||
|
||||
const stopProcess = async (PID: number) => { |
||||
try { |
||||
await useDeleteData(StopProcess, { PID: PID }, i18n.global.t('process.stopProcessWarn', [PID])); |
||||
} catch (error) {} |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
initProcess(); |
||||
}); |
||||
|
||||
onUnmounted(() => { |
||||
closeSocket(); |
||||
}); |
||||
</script> |
Loading…
Reference in new issue