mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-07 17:10:07 +08:00
feat: 增加进程管理 (#1476)
This commit is contained in:
parent
c403eb55b1
commit
38c0d290e7
@ -49,4 +49,5 @@ var (
|
|||||||
upgradeService = service.NewIUpgradeService()
|
upgradeService = service.NewIUpgradeService()
|
||||||
|
|
||||||
runtimeService = service.NewRuntimeService()
|
runtimeService = service.NewRuntimeService()
|
||||||
|
processService = service.NewIProcessService()
|
||||||
)
|
)
|
||||||
|
@ -734,19 +734,12 @@ var wsUpgrade = websocket.Upgrader{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var WsManager = websocket2.Manager{
|
|
||||||
Group: make(map[string]*websocket2.Client),
|
|
||||||
Register: make(chan *websocket2.Client, 128),
|
|
||||||
UnRegister: make(chan *websocket2.Client, 128),
|
|
||||||
ClientCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseApi) Ws(c *gin.Context) {
|
func (b *BaseApi) Ws(c *gin.Context) {
|
||||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wsClient := websocket2.NewWsClient("wsClient", ws)
|
wsClient := websocket2.NewWsClient("fileClient", ws)
|
||||||
go wsClient.Read()
|
go wsClient.Read()
|
||||||
go wsClient.Write()
|
go wsClient.Write()
|
||||||
}
|
}
|
||||||
|
40
backend/app/api/v1/process.go
Normal file
40
backend/app/api/v1/process.go
Normal file
@ -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)
|
||||||
|
}
|
5
backend/app/dto/request/process.go
Normal file
5
backend/app/dto/request/process.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
type ProcessReq struct {
|
||||||
|
PID int32 `json:"PID" validate:"required"`
|
||||||
|
}
|
27
backend/app/service/process.go
Normal file
27
backend/app/service/process.go
Normal file
@ -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
|
||||||
|
}
|
@ -86,6 +86,7 @@ func Routers() *gin.Engine {
|
|||||||
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
|
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
|
||||||
systemRouter.InitNginxRouter(PrivateGroup)
|
systemRouter.InitNginxRouter(PrivateGroup)
|
||||||
systemRouter.InitRuntimeRouter(PrivateGroup)
|
systemRouter.InitRuntimeRouter(PrivateGroup)
|
||||||
|
systemRouter.InitProcessRouter(PrivateGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Router
|
return Router
|
||||||
|
@ -20,6 +20,7 @@ type RouterGroup struct {
|
|||||||
DatabaseRouter
|
DatabaseRouter
|
||||||
NginxRouter
|
NginxRouter
|
||||||
RuntimeRouter
|
RuntimeRouter
|
||||||
|
ProcessRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
var RouterGroupApp = new(RouterGroup)
|
var RouterGroupApp = new(RouterGroup)
|
||||||
|
20
backend/router/ro_process.go
Normal file
20
backend/router/ro_process.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
68
backend/utils/ps/ps_test.go
Normal file
68
backend/utils/ps/ps_test.go
Normal file
@ -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,17 +1,9 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WsMsg struct {
|
|
||||||
Type string
|
|
||||||
Keys []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID string
|
ID string
|
||||||
Socket *websocket.Conn
|
Socket *websocket.Conn
|
||||||
@ -35,9 +27,7 @@ func (c *Client) Read() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := &WsMsg{}
|
ProcessData(c, message)
|
||||||
_ = json.Unmarshal(message, msg)
|
|
||||||
ProcessData(c, msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,21 +43,3 @@ func (c *Client) Write() {
|
|||||||
_ = c.Socket.WriteMessage(websocket.TextMessage, message)
|
_ = c.Socket.WriteMessage(websocket.TextMessage, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessData(c *Client, msg *WsMsg) {
|
|
||||||
if msg.Type == "wget" {
|
|
||||||
var res []files.Process
|
|
||||||
for _, k := range msg.Keys {
|
|
||||||
value, err := global.CACHE.Get(k)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get cache error,err %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
process := &files.Process{}
|
|
||||||
_ = json.Unmarshal(value, process)
|
|
||||||
res = append(res, *process)
|
|
||||||
}
|
|
||||||
reByte, _ := json.Marshal(res)
|
|
||||||
c.Msg <- reByte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
222
backend/utils/websocket/process_data.go
Normal file
222
backend/utils/websocket/process_data.go
Normal file
@ -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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5
frontend/src/api/interface/process.ts
Normal file
5
frontend/src/api/interface/process.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export namespace Process {
|
||||||
|
export interface StopReq {
|
||||||
|
PID: number;
|
||||||
|
}
|
||||||
|
}
|
6
frontend/src/api/modules/process.ts
Normal file
6
frontend/src/api/modules/process.ts
Normal file
@ -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);
|
||||||
|
};
|
@ -75,11 +75,16 @@ function handleSelectionChange(row: any) {
|
|||||||
emit('update:selects', row);
|
emit('update:selects', row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sort(prop: string, order: string) {
|
||||||
|
tableRef.value.refElTable.sort(prop, order);
|
||||||
|
}
|
||||||
|
|
||||||
function clearSelects() {
|
function clearSelects() {
|
||||||
tableRef.value.refElTable.clearSelection();
|
tableRef.value.refElTable.clearSelection();
|
||||||
}
|
}
|
||||||
defineExpose({
|
defineExpose({
|
||||||
clearSelects,
|
clearSelects,
|
||||||
|
sort,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -241,6 +241,8 @@ const message = {
|
|||||||
logs: 'Log',
|
logs: 'Log',
|
||||||
ssl: 'Certificate',
|
ssl: 'Certificate',
|
||||||
runtime: 'Runtime',
|
runtime: 'Runtime',
|
||||||
|
processManage: 'Process',
|
||||||
|
process: 'Process',
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
@ -1619,6 +1621,40 @@ const message = {
|
|||||||
rebuildHelper:
|
rebuildHelper:
|
||||||
'After editing the extension, you need to go to the [App Store-Installed] page to rebuild the PHP application to take effect',
|
'After editing the extension, you need to go to the [App Store-Installed] page to rebuild the PHP application to take effect',
|
||||||
},
|
},
|
||||||
|
process: {
|
||||||
|
pid: 'Process ID',
|
||||||
|
ppid: 'Parent process ID',
|
||||||
|
numThreads: 'Threads',
|
||||||
|
username: 'User',
|
||||||
|
memory: 'Memory',
|
||||||
|
diskRead: 'Disk read',
|
||||||
|
diskWrite: 'Disk write',
|
||||||
|
netSent: 'uplink',
|
||||||
|
netRecv: 'downstream',
|
||||||
|
numConnections: 'Connections',
|
||||||
|
startTime: 'Start time',
|
||||||
|
status: 'Status',
|
||||||
|
running: 'Running',
|
||||||
|
sleep: 'sleep',
|
||||||
|
stop: 'stop',
|
||||||
|
idle: 'idle',
|
||||||
|
zombie: 'zombie process',
|
||||||
|
wait: 'waiting',
|
||||||
|
lock: 'lock',
|
||||||
|
blocked: 'blocked',
|
||||||
|
cmdLine: 'Start command',
|
||||||
|
basic: 'Basic information',
|
||||||
|
mem: 'Memory information',
|
||||||
|
openFiles: 'File Open',
|
||||||
|
file: 'File',
|
||||||
|
env: 'Environment variable',
|
||||||
|
noenv: 'None',
|
||||||
|
net: 'Network connection',
|
||||||
|
laddr: 'Source address/port',
|
||||||
|
raddr: 'Destination address/port',
|
||||||
|
stopProcess: 'End',
|
||||||
|
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})? This operation cannot be rolled back',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -244,6 +244,8 @@ const message = {
|
|||||||
toolbox: '工具箱',
|
toolbox: '工具箱',
|
||||||
logs: '日志审计',
|
logs: '日志审计',
|
||||||
runtime: '运行环境',
|
runtime: '运行环境',
|
||||||
|
processManage: '进程管理',
|
||||||
|
process: '进程',
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
overview: '概览',
|
overview: '概览',
|
||||||
@ -1559,6 +1561,40 @@ const message = {
|
|||||||
extendHelper: '列表中不存在的扩展,可以手动输入之后选择,例:输入 sockets ,然后在下拉列表中选择第一个',
|
extendHelper: '列表中不存在的扩展,可以手动输入之后选择,例:输入 sockets ,然后在下拉列表中选择第一个',
|
||||||
rebuildHelper: '编辑扩展后需要去【应用商店-已安装】页面【重建】PHP 应用之后才能生效',
|
rebuildHelper: '编辑扩展后需要去【应用商店-已安装】页面【重建】PHP 应用之后才能生效',
|
||||||
},
|
},
|
||||||
|
process: {
|
||||||
|
pid: '进程ID',
|
||||||
|
ppid: '父进程ID',
|
||||||
|
numThreads: '线程',
|
||||||
|
username: '用户',
|
||||||
|
memory: '内存',
|
||||||
|
diskRead: '磁盘读',
|
||||||
|
diskWrite: '磁盘写',
|
||||||
|
netSent: '上行',
|
||||||
|
netRecv: '下行',
|
||||||
|
numConnections: '连接',
|
||||||
|
startTime: '启动时间',
|
||||||
|
status: '状态',
|
||||||
|
running: '运行中',
|
||||||
|
sleep: '睡眠',
|
||||||
|
stop: '停止',
|
||||||
|
idle: '空闲',
|
||||||
|
zombie: '僵尸进程',
|
||||||
|
wait: '等待',
|
||||||
|
lock: '锁定',
|
||||||
|
blocked: '阻塞',
|
||||||
|
cmdLine: '启动命令',
|
||||||
|
basic: '基本信息',
|
||||||
|
mem: '内存信息',
|
||||||
|
openFiles: '文件打开',
|
||||||
|
file: '文件',
|
||||||
|
env: '环境变量',
|
||||||
|
noenv: '无',
|
||||||
|
net: '网络连接',
|
||||||
|
laddr: '源地址/端口',
|
||||||
|
raddr: '目标地址/端口',
|
||||||
|
stopProcess: '结束',
|
||||||
|
stopProcessWarn: '是否确定结束此进程 (PID:{0})?此操作不可回滚',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export default {
|
export default {
|
||||||
...fit2cloudZhLocale,
|
...fit2cloudZhLocale,
|
||||||
|
@ -69,6 +69,17 @@ const hostRouter = {
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hosts/process/process',
|
||||||
|
name: 'Process',
|
||||||
|
component: () => import('@/views/host/process/process/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'menu.processManage',
|
||||||
|
activeMenu: '/hosts/process/process',
|
||||||
|
keepAlive: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/hosts/ssh/ssh',
|
path: '/hosts/ssh/ssh',
|
||||||
name: 'SSH',
|
name: 'SSH',
|
||||||
|
20
frontend/src/views/host/process/index.vue
Normal file
20
frontend/src/views/host/process/index.vue
Normal file
@ -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>
|
129
frontend/src/views/host/process/process/detail/index.vue
Normal file
129
frontend/src/views/host/process/process/detail/index.vue
Normal file
@ -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>
|
284
frontend/src/views/host/process/process/index.vue
Normal file
284
frontend/src/views/host/process/process/index.vue
Normal file
@ -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…
x
Reference in New Issue
Block a user