mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 20:49:03 +08:00
feat: 优化进程守护状态获取方式 (#1943)
This commit is contained in:
parent
b78b0e67ac
commit
35b8532eaa
@ -189,6 +189,16 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
helper.SuccessWithData(c, configs)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Load Supervisor process status
|
||||
// @Description 获取 Supervisor 进程状态
|
||||
// @Success 200 {array} response.ProcessStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/supervisor/process/load [post]
|
||||
func (b *BaseApi) LoadProcessStatus(c *gin.Context) {
|
||||
helper.SuccessWithData(c, hostToolService.LoadProcessStatus())
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get Supervisor process config
|
||||
// @Description 操作 Supervisor 进程文件
|
||||
|
@ -3,6 +3,14 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@ -14,11 +22,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/ini.v1"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HostToolService struct{}
|
||||
@ -31,6 +34,7 @@ type IHostToolService interface {
|
||||
GetToolLog(req request.HostToolLogReq) (string, error)
|
||||
OperateSupervisorProcess(req request.SupervisorProcessConfig) error
|
||||
GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error)
|
||||
LoadProcessStatus() []response.ProcessStatus
|
||||
OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error)
|
||||
}
|
||||
|
||||
@ -364,6 +368,57 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) LoadProcessStatus() []response.ProcessStatus {
|
||||
var datas []response.ProcessStatus
|
||||
statuLines, err := cmd.Exec("supervisorctl status")
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
lines := strings.Split(string(statuLines), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 1 {
|
||||
datas = append(datas, response.ProcessStatus{Name: fields[0]})
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(datas))
|
||||
for i := 0; i < len(datas); i++ {
|
||||
go func(index int) {
|
||||
for t := 0; t < 3; t++ {
|
||||
status, err := cmd.ExecWithTimeOut(fmt.Sprintf("supervisorctl status %s", datas[index].Name), 3*time.Second)
|
||||
if err != nil {
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(status)
|
||||
if len(fields) < 5 {
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
datas[index].Name = fields[0]
|
||||
datas[index].Status = fields[1]
|
||||
if fields[1] != "RUNNING" {
|
||||
datas[index].Msg = strings.Join(fields[2:], " ")
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
datas[index].PID = strings.TrimSuffix(fields[3], ",")
|
||||
datas[index].Uptime = fields[5]
|
||||
break
|
||||
}
|
||||
if len(datas[index].Status) == 0 {
|
||||
datas[index].Status = "FATAL"
|
||||
datas[index].Msg = "Timeout for getting process status"
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
return datas
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) {
|
||||
var (
|
||||
result []response.SupervisorProcessConfig
|
||||
@ -400,7 +455,6 @@ func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorPro
|
||||
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
|
||||
config.Numprocs = numprocs.Value()
|
||||
}
|
||||
_ = getProcessStatus(&config)
|
||||
result = append(result, config)
|
||||
}
|
||||
}
|
||||
@ -528,29 +582,3 @@ func getProcessName(name, numprocs string) []string {
|
||||
}
|
||||
return processNames
|
||||
}
|
||||
|
||||
func getProcessStatus(config *response.SupervisorProcessConfig) error {
|
||||
var (
|
||||
processNames = []string{"status"}
|
||||
)
|
||||
processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...)
|
||||
output, _ := exec.Command("supervisorctl", processNames...).Output()
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 5 {
|
||||
status := response.ProcessStatus{
|
||||
Name: fields[0],
|
||||
Status: fields[1],
|
||||
}
|
||||
if fields[1] == "RUNNING" {
|
||||
status.PID = strings.TrimSuffix(fields[3], ",")
|
||||
status.Uptime = fields[5]
|
||||
} else {
|
||||
status.Msg = strings.Join(fields[2:], " ")
|
||||
}
|
||||
config.Status = append(config.Status, status)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/tool/operate", baseApi.OperateTool)
|
||||
hostRouter.POST("/tool/config", baseApi.OperateToolConfig)
|
||||
hostRouter.POST("/tool/log", baseApi.GetToolLog)
|
||||
hostRouter.POST("/tool/supervisor/process/load", baseApi.LoadProcessStatus)
|
||||
hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess)
|
||||
hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess)
|
||||
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
|
||||
|
@ -6340,6 +6340,31 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/tool/supervisor/process/load": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 Supervisor 进程状态",
|
||||
"tags": [
|
||||
"Host tool"
|
||||
],
|
||||
"summary": "Load Supervisor process status",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/response.ProcessStatus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/hosts": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -17024,6 +17049,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.ProcessStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"PID": {
|
||||
"type": "string"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"uptime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteAcmeAccountDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -6333,6 +6333,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/tool/supervisor/process/load": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 Supervisor 进程状态",
|
||||
"tags": [
|
||||
"Host tool"
|
||||
],
|
||||
"summary": "Load Supervisor process status",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/response.ProcessStatus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/hosts": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -17017,6 +17042,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.ProcessStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"PID": {
|
||||
"type": "string"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"uptime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteAcmeAccountDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -3714,6 +3714,19 @@ definitions:
|
||||
uploadMaxSize:
|
||||
type: string
|
||||
type: object
|
||||
response.ProcessStatus:
|
||||
properties:
|
||||
PID:
|
||||
type: string
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
uptime:
|
||||
type: string
|
||||
type: object
|
||||
response.WebsiteAcmeAccountDTO:
|
||||
properties:
|
||||
createdAt:
|
||||
@ -7880,6 +7893,21 @@ paths:
|
||||
formatEN: '[operate] Supervisor Process Config file'
|
||||
formatZH: '[operate] Supervisor 进程文件 '
|
||||
paramKeys: []
|
||||
/host/tool/supervisor/process/load:
|
||||
post:
|
||||
description: 获取 Supervisor 进程状态
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/response.ProcessStatus'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load Supervisor process status
|
||||
tags:
|
||||
- Host tool
|
||||
/hosts:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -29,6 +29,10 @@ export const OperateSupervisorProcess = (req: HostTool.ProcessReq) => {
|
||||
return http.post<any>(`/hosts/tool/supervisor/process`, req, 100000);
|
||||
};
|
||||
|
||||
export const LoadProcessStatus = () => {
|
||||
return http.post<Array<HostTool.ProcessStatus>>(`/hosts/tool/supervisor/process/load`, {}, 40000);
|
||||
};
|
||||
|
||||
export const GetSupervisorProcess = () => {
|
||||
return http.get<HostTool.SupersivorProcess>(`/hosts/tool/supervisor/process`);
|
||||
};
|
||||
|
@ -1671,10 +1671,10 @@ const message = {
|
||||
config: 'Supervisor configuration',
|
||||
primaryConfig: 'Main configuration file location',
|
||||
notSupportCrl: 'The supervisorctl is not detected, please refer to the official document for installation',
|
||||
user: 'start user',
|
||||
command: 'start command',
|
||||
dir: 'run directory',
|
||||
numprocs: 'Number of processes',
|
||||
user: 'User',
|
||||
command: 'Command',
|
||||
dir: 'Directory',
|
||||
numprocs: 'Numprocs',
|
||||
initWarn:
|
||||
'The initialization operation needs to modify the [include] files parameter of the configuration file, the directory where the modified service configuration file is located: 1panel installation directory/1panel/tools/supervisord/supervisor.d/',
|
||||
operatorHelper: 'Operation {1} will be performed on {0}, continue? ',
|
||||
|
@ -73,6 +73,8 @@ const handleReady = (payload) => {
|
||||
};
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const em = defineEmits(['search']);
|
||||
|
||||
const getContent = () => {
|
||||
loading.value = true;
|
||||
OperateSupervisorProcessFile(req)
|
||||
@ -114,8 +116,9 @@ const submit = () => {
|
||||
loading.value = true;
|
||||
OperateSupervisorProcessFile(updateReq)
|
||||
.then(() => {
|
||||
em('search');
|
||||
open.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
getContent();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
|
@ -20,28 +20,41 @@
|
||||
</template>
|
||||
<template #main v-if="showTable">
|
||||
<ComplexTable :data="data" :class="{ mask: !supervisorStatus.isRunning }">
|
||||
<el-table-column :label="$t('commons.table.name')" fix prop="name" width="150px"></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
fix
|
||||
prop="name"
|
||||
min-width="80px"
|
||||
show-overflow-tooltip
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('tool.supervisor.command')"
|
||||
prop="command"
|
||||
min-width="100px"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('tool.supervisor.dir')"
|
||||
prop="dir"
|
||||
min-width="100px"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('tool.supervisor.user')" prop="user" width="100px"></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('tool.supervisor.user')"
|
||||
prop="user"
|
||||
show-overflow-tooltip
|
||||
min-width="50px"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('tool.supervisor.numprocs')"
|
||||
prop="numprocs"
|
||||
width="100px"
|
||||
min-width="60px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('tool.supervisor.manage')" width="100px">
|
||||
<el-table-column :label="$t('tool.supervisor.manage')" min-width="80px">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.status && row.status.length > 0">
|
||||
<div v-if="row.status && row.status.length > 0 && row.hasLoad">
|
||||
<el-button
|
||||
v-if="checkStatus(row.status) === 'RUNNING'"
|
||||
link
|
||||
@ -70,11 +83,14 @@
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="!row.hasLoad">
|
||||
<el-button link loading></el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" width="100px">
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="60px">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.status">
|
||||
<div v-if="row.hasLoad">
|
||||
<el-popover placement="bottom" :width="600" trigger="hover">
|
||||
<template #reference>
|
||||
<el-button type="primary" link v-if="row.status.length > 1">
|
||||
@ -111,6 +127,9 @@
|
||||
</el-table>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="!row.hasLoad">
|
||||
<el-button link loading></el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
@ -118,7 +137,7 @@
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
:fixed="mobile ? false : 'right'"
|
||||
width="250px"
|
||||
width="280px"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
@ -126,7 +145,7 @@
|
||||
<ConfigSuperVisor v-if="setSuperVisor" />
|
||||
</LayoutContent>
|
||||
<Create ref="createRef" @close="search"></Create>
|
||||
<File ref="fileRef"></File>
|
||||
<File ref="fileRef" @search="search"></File>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -138,7 +157,7 @@ import ConfigSuperVisor from './config/index.vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import Create from './create/index.vue';
|
||||
import File from './file/index.vue';
|
||||
import { GetSupervisorProcess, OperateSupervisorProcess } from '@/api/modules/host-tool';
|
||||
import { GetSupervisorProcess, LoadProcessStatus, OperateSupervisorProcess } from '@/api/modules/host-tool';
|
||||
import { GlobalStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
import { HostTool } from '@/api/interface/host-tool';
|
||||
@ -193,6 +212,7 @@ const openCreate = () => {
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
loadStatus();
|
||||
try {
|
||||
const res = await GetSupervisorProcess();
|
||||
data.value = res.data;
|
||||
@ -200,6 +220,25 @@ const search = async () => {
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
const res = await LoadProcessStatus();
|
||||
let stats = res.data || [];
|
||||
if (stats.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const process of data.value) {
|
||||
process.status = [];
|
||||
for (const item of stats) {
|
||||
if (process.name === item.name) {
|
||||
process.status.push(item);
|
||||
}
|
||||
}
|
||||
if (process.status.length !== 0) {
|
||||
process.hasLoad = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user