feat: 增加进程管理 (#1476)
This commit is contained in:
		| @@ -49,4 +49,5 @@ var ( | ||||
| 	upgradeService  = service.NewIUpgradeService() | ||||
|  | ||||
| 	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) { | ||||
| 	ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	wsClient := websocket2.NewWsClient("wsClient", ws) | ||||
| 	wsClient := websocket2.NewWsClient("fileClient", ws) | ||||
| 	go wsClient.Read() | ||||
| 	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.InitNginxRouter(PrivateGroup) | ||||
| 		systemRouter.InitRuntimeRouter(PrivateGroup) | ||||
| 		systemRouter.InitProcessRouter(PrivateGroup) | ||||
| 	} | ||||
|  | ||||
| 	return Router | ||||
|   | ||||
| @@ -20,6 +20,7 @@ type RouterGroup struct { | ||||
| 	DatabaseRouter | ||||
| 	NginxRouter | ||||
| 	RuntimeRouter | ||||
| 	ProcessRouter | ||||
| } | ||||
|  | ||||
| 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 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/files" | ||||
| 	"github.com/gorilla/websocket" | ||||
| ) | ||||
|  | ||||
| type WsMsg struct { | ||||
| 	Type string | ||||
| 	Keys []string | ||||
| } | ||||
|  | ||||
| type Client struct { | ||||
| 	ID     string | ||||
| 	Socket *websocket.Conn | ||||
| @@ -35,9 +27,7 @@ func (c *Client) Read() { | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		msg := &WsMsg{} | ||||
| 		_ = json.Unmarshal(message, msg) | ||||
| 		ProcessData(c, msg) | ||||
| 		ProcessData(c, message) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -53,21 +43,3 @@ func (c *Client) Write() { | ||||
| 		_ = 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); | ||||
| } | ||||
|  | ||||
| function sort(prop: string, order: string) { | ||||
|     tableRef.value.refElTable.sort(prop, order); | ||||
| } | ||||
|  | ||||
| function clearSelects() { | ||||
|     tableRef.value.refElTable.clearSelection(); | ||||
| } | ||||
| defineExpose({ | ||||
|     clearSelects, | ||||
|     sort, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -241,6 +241,8 @@ const message = { | ||||
|         logs: 'Log', | ||||
|         ssl: 'Certificate', | ||||
|         runtime: 'Runtime', | ||||
|         processManage: 'Process', | ||||
|         process: 'Process', | ||||
|     }, | ||||
|     home: { | ||||
|         overview: 'Overview', | ||||
| @@ -1619,6 +1621,40 @@ const message = { | ||||
|         rebuildHelper: | ||||
|             '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 { | ||||
|   | ||||
| @@ -244,6 +244,8 @@ const message = { | ||||
|         toolbox: '工具箱', | ||||
|         logs: '日志审计', | ||||
|         runtime: '运行环境', | ||||
|         processManage: '进程管理', | ||||
|         process: '进程', | ||||
|     }, | ||||
|     home: { | ||||
|         overview: '概览', | ||||
| @@ -1559,6 +1561,40 @@ const message = { | ||||
|         extendHelper: '列表中不存在的扩展,可以手动输入之后选择,例:输入 sockets ,然后在下拉列表中选择第一个', | ||||
|         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 { | ||||
|     ...fit2cloudZhLocale, | ||||
|   | ||||
| @@ -69,6 +69,17 @@ const hostRouter = { | ||||
|                 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', | ||||
|             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> | ||||
		Reference in New Issue
	
	Block a user
	 zhengkunwang223
					zhengkunwang223