From 65a46796ea0280527b52b9fc83de01656164a78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 26 Jul 2023 02:14:26 +0800 Subject: [PATCH] feat: supervisor plugin and optimize code --- .../plugins/php74/php74_controller.go | 3 +- .../plugins/php80/php80_controller.go | 3 +- .../supervisor/supervisor_controller.go | 390 +++++++++++++++ app/plugins/supervisor/supervisor.go | 13 + app/services/plugin.go | 12 + public/panel/views/cron.html | 8 +- public/panel/views/plugins/supervisor.html | 451 ++++++++++++++++++ .../views/plugins/supervisor/add_process.html | 94 ++++ routes/plugin.go | 24 + scripts/supervisor/install.sh | 38 ++ scripts/supervisor/uninstall.sh | 33 ++ 11 files changed, 1063 insertions(+), 6 deletions(-) create mode 100644 app/http/controllers/plugins/supervisor/supervisor_controller.go create mode 100644 app/plugins/supervisor/supervisor.go create mode 100644 public/panel/views/plugins/supervisor.html create mode 100644 public/panel/views/plugins/supervisor/add_process.html create mode 100644 scripts/supervisor/install.sh create mode 100644 scripts/supervisor/uninstall.sh diff --git a/app/http/controllers/plugins/php74/php74_controller.go b/app/http/controllers/plugins/php74/php74_controller.go index 8247718055..9d0f42a6cd 100644 --- a/app/http/controllers/plugins/php74/php74_controller.go +++ b/app/http/controllers/plugins/php74/php74_controller.go @@ -35,8 +35,7 @@ func (c *Php74Controller) Status(ctx http.Context) { return } - out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") return diff --git a/app/http/controllers/plugins/php80/php80_controller.go b/app/http/controllers/plugins/php80/php80_controller.go index 72f969944a..ac92614ded 100644 --- a/app/http/controllers/plugins/php80/php80_controller.go +++ b/app/http/controllers/plugins/php80/php80_controller.go @@ -35,8 +35,7 @@ func (c *Php80Controller) Status(ctx http.Context) { return } - out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") return diff --git a/app/http/controllers/plugins/supervisor/supervisor_controller.go b/app/http/controllers/plugins/supervisor/supervisor_controller.go new file mode 100644 index 0000000000..ecf1f09650 --- /dev/null +++ b/app/http/controllers/plugins/supervisor/supervisor_controller.go @@ -0,0 +1,390 @@ +package supervisor + +import ( + "strconv" + "strings" + + "github.com/goravel/framework/contracts/http" + "panel/pkg/tools" + + "panel/app/http/controllers" +) + +type SupervisorController struct { + ServiceName string +} + +func NewSupervisorController() *SupervisorController { + var serviceName string + if tools.IsRHEL() { + serviceName = "supervisord" + } else { + serviceName = "supervisor" + } + + return &SupervisorController{ + ServiceName: serviceName, + } +} + +// Status 状态 +func (r *SupervisorController) Status(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + status := tools.ExecShell(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`) + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Start 启动 +func (r *SupervisorController) Start(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + tools.ExecShell(`systemctl start ` + r.ServiceName) + status := tools.ExecShell(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`) + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Stop 停止 +func (r *SupervisorController) Stop(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + tools.ExecShell(`systemctl stop ` + r.ServiceName) + status := tools.ExecShell(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`) + if status != "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Restart 重启 +func (r *SupervisorController) Restart(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + tools.ExecShell(`systemctl restart ` + r.ServiceName) + status := tools.ExecShell(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`) + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Reload 重载 +func (r *SupervisorController) Reload(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + tools.ExecShell(`systemctl reload ` + r.ServiceName) + status := tools.ExecShell(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`) + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Log 日志 +func (r *SupervisorController) Log(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + log := tools.ExecShell(`tail -n 200 /var/log/supervisor/supervisord.log`) + controllers.Success(ctx, log) +} + +// ClearLog 清空日志 +func (r *SupervisorController) ClearLog(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + tools.ExecShell(`echo "" > /var/log/supervisor/supervisord.log`) + controllers.Success(ctx, nil) +} + +// Config 获取配置 +func (r *SupervisorController) Config(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + var config string + if tools.IsRHEL() { + config = tools.ReadFile(`/etc/supervisord.conf`) + } else { + config = tools.ReadFile(`/etc/supervisor/supervisord.conf`) + } + controllers.Success(ctx, config) +} + +// SaveConfig 保存配置 +func (r *SupervisorController) SaveConfig(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + config := ctx.Request().Input("config") + if tools.IsRHEL() { + tools.WriteFile(`/etc/supervisord.conf`, config, 0644) + } else { + tools.WriteFile(`/etc/supervisor/supervisord.conf`, config, 0644) + } + + tools.ExecShell(`systemctl restart ` + r.ServiceName) + controllers.Success(ctx, nil) +} + +// Processes 进程列表 +func (r *SupervisorController) Processes(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + + type process struct { + Name string `json:"name"` + Status string `json:"status"` + Pid string `json:"pid"` + Uptime string `json:"uptime"` + } + + out := tools.ExecShell(`supervisorctl status | awk '{print $1}'`) + var processList []process + for _, line := range strings.Split(out, "\n") { + if len(line) == 0 { + continue + } + + var p process + p.Name = line + p.Status = tools.ExecShell(`supervisorctl status ` + line + ` | awk '{print $2}'`) + if p.Status == "RUNNING" { + p.Pid = strings.ReplaceAll(tools.ExecShell(`supervisorctl status `+line+` | awk '{print $4}'`), ",", "") + p.Uptime = tools.ExecShell(`supervisorctl status ` + line + ` | awk '{print $6}'`) + } else { + p.Pid = "-" + p.Uptime = "-" + } + processList = append(processList, p) + } + + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(processList) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []process{}, + }) + return + } + if endIndex > len(processList) { + endIndex = len(processList) + } + pagedProcessList := processList[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(processList), + "items": pagedProcessList, + }) +} + +// StartProcess 启动进程 +func (r *SupervisorController) StartProcess(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + tools.ExecShell(`supervisorctl start ` + process) + controllers.Success(ctx, nil) +} + +// StopProcess 停止进程 +func (r *SupervisorController) StopProcess(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + tools.ExecShell(`supervisorctl stop ` + process) + controllers.Success(ctx, nil) +} + +// RestartProcess 重启进程 +func (r *SupervisorController) RestartProcess(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + tools.ExecShell(`supervisorctl restart ` + process) + controllers.Success(ctx, nil) +} + +// ProcessLog 进程日志 +func (r *SupervisorController) ProcessLog(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + var logPath string + if tools.IsRHEL() { + logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + } else { + logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + } + + log := tools.ExecShell(`tail -n 200 ` + logPath) + controllers.Success(ctx, log) +} + +// ClearProcessLog 清空进程日志 +func (r *SupervisorController) ClearProcessLog(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + var logPath string + if tools.IsRHEL() { + logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + } else { + logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + } + + tools.ExecShell(`echo "" > ` + logPath) + controllers.Success(ctx, nil) +} + +// ProcessConfig 获取进程配置 +func (r *SupervisorController) ProcessConfig(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Query("process") + var config string + if tools.IsRHEL() { + config = tools.ReadFile(`/etc/supervisord.d/` + process + `.conf`) + } else { + config = tools.ReadFile(`/etc/supervisor/conf.d/` + process + `.conf`) + } + + controllers.Success(ctx, config) +} + +// SaveProcessConfig 保存进程配置 +func (r *SupervisorController) SaveProcessConfig(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + config := ctx.Request().Input("config") + if tools.IsRHEL() { + tools.WriteFile(`/etc/supervisord.d/`+process+`.conf`, config, 0644) + } else { + tools.WriteFile(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644) + } + tools.ExecShell(`supervisorctl reread`) + tools.ExecShell(`supervisorctl update`) + tools.ExecShell(`supervisorctl start ` + process) + + controllers.Success(ctx, nil) +} + +// AddProcess 添加进程 +func (r *SupervisorController) AddProcess(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "name": "required|alpha_dash", + "user": "required|alpha_dash", + "path": "required", + "command": "required", + "num": "required", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + return + } + + name := ctx.Request().Input("name") + user := ctx.Request().Input("user") + path := ctx.Request().Input("path") + command := ctx.Request().Input("command") + num := ctx.Request().InputInt("num", 1) + config := `[program:` + name + `] +command=` + command + ` +process_name=%(program_name)s +directory=` + path + ` +autostart=true +autorestart=true +user=` + user + ` +numprocs=` + strconv.Itoa(num) + ` +redirect_stderr=true +stdout_logfile=/var/log/supervisor/` + name + `.log +stdout_logfile_maxbytes=2MB +` + if tools.IsRHEL() { + tools.WriteFile(`/etc/supervisord.d/`+name+`.conf`, config, 0644) + } else { + tools.WriteFile(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644) + } + tools.ExecShell(`supervisorctl reread`) + tools.ExecShell(`supervisorctl update`) + tools.ExecShell(`supervisorctl start ` + name) + + controllers.Success(ctx, nil) +} + +// DeleteProcess 删除进程 +func (r *SupervisorController) DeleteProcess(ctx http.Context) { + if !controllers.Check(ctx, "supervisor") { + return + } + + process := ctx.Request().Input("process") + tools.ExecShell(`supervisorctl stop ` + process) + var logPath string + if tools.IsRHEL() { + logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + tools.RemoveFile(`/etc/supervisord.d/` + process + `.conf`) + } else { + logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`) + tools.RemoveFile(`/etc/supervisor/conf.d/` + process + `.conf`) + } + tools.RemoveFile(logPath) + tools.ExecShell(`supervisorctl reread`) + tools.ExecShell(`supervisorctl update`) + + controllers.Success(ctx, nil) +} diff --git a/app/plugins/supervisor/supervisor.go b/app/plugins/supervisor/supervisor.go new file mode 100644 index 0000000000..f8cf558bf0 --- /dev/null +++ b/app/plugins/supervisor/supervisor.go @@ -0,0 +1,13 @@ +package supervisor + +var ( + Name = "Supervisor" + Description = "Supervisor 是一个客户端/服务器系统,允许用户监视和控制类 UNIX 操作系统上的多个进程。" + Slug = "supervisor" + Version = "4.2.5" + Requires = []string{} + Excludes = []string{} + Install = `bash /www/panel/scripts/supervisor/install.sh` + Uninstall = `bash /www/panel/scripts/supervisor/uninstall.sh` + Update = `bash /www/panel/scripts/supervisor/install.sh` +) diff --git a/app/services/plugin.go b/app/services/plugin.go index c91c2f5af4..d8bf7d1aa4 100644 --- a/app/services/plugin.go +++ b/app/services/plugin.go @@ -12,6 +12,7 @@ import ( "panel/app/plugins/php80" "panel/app/plugins/phpmyadmin" "panel/app/plugins/s3fs" + "panel/app/plugins/supervisor" ) // PanelPlugin 插件元数据结构 @@ -132,6 +133,17 @@ func (r *PluginImpl) All() []PanelPlugin { Uninstall: s3fs.Uninstall, Update: s3fs.Update, }) + p = append(p, PanelPlugin{ + Name: supervisor.Name, + Description: supervisor.Description, + Slug: supervisor.Slug, + Version: supervisor.Version, + Requires: supervisor.Requires, + Excludes: supervisor.Excludes, + Install: supervisor.Install, + Uninstall: supervisor.Uninstall, + Update: supervisor.Update, + }) return p } diff --git a/public/panel/views/cron.html b/public/panel/views/cron.html index 630a6cbdb4..9683e91518 100644 --- a/public/panel/views/cron.html +++ b/public/panel/views/cron.html @@ -205,7 +205,7 @@ if (obj.event === 'log') { // 打开日志弹窗 admin.popup({ - title: '日志' + title: '日志 - ' + data.name , area: ['80%', '80%'] , @@ -214,10 +214,12 @@ content: '
'
                     ,
                     success: function (layero, index) {
+                        index2 = layer.msg('正在获取日志...', {icon: 16, time: 0, shade: 0.3});
                         admin.req({
                             url: '/api/panel/cron/log?id=' + data.id
                             , type: 'GET'
                             , success: function (res) {
+                                layer.close(index2);
                                 if (res.code === 0) {
                                     $('#cron-log-view').html(res.data);
                                 } else {
@@ -236,7 +238,7 @@
                     , success: function (res) {
                         if (res.code === 0) {
                             admin.popup({
-                                title: '编辑'
+                                title: '编辑 - ' + data.name
                                 ,
                                 area: ['80%', '80%']
                                 ,
@@ -258,6 +260,7 @@
                                         }
                                     });
                                     $('#cron-edit-' + data.id).click(function () {
+                                        index2 = layer.msg('正在保存...', {icon: 16, time: 0, shade: 0.3});
                                         admin.req({
                                             url: '/api/panel/cron/update'
                                             , type: 'POST'
@@ -271,6 +274,7 @@
                                                 if (res.code === 0) {
                                                     layer.msg('保存成功', {icon: 1, time: 1000});
                                                     table.reload('panel-cron');
+                                                    layer.close(index2);
                                                     layer.close(index);
                                                 } else {
                                                     layer.msg(res.message, {icon: 2, time: 1000});
diff --git a/public/panel/views/plugins/supervisor.html b/public/panel/views/plugins/supervisor.html
new file mode 100644
index 0000000000..afeb2ebf8f
--- /dev/null
+++ b/public/panel/views/plugins/supervisor.html
@@ -0,0 +1,451 @@
+
+Supervisor
+
+
+
+
+
Supervisor 管理
+
+
+
    +
  • 运行状态
  • +
  • 进程列表
  • +
  • 配置修改
  • +
  • 运行日志
  • +
+
+
+
+ 当前状态:获取中
+
+ + + + +
+
+
+
+ + + + +
+
+
此处修改的是Supervisor主配置文件,如果你不了解各参数的含义,请不要随意修改!
+ 提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换! +
+
+
+ +
+
+
+
+ +
+
+                                    获取中...
+                                
+
+
+
+
+
+
+
+
+ + diff --git a/public/panel/views/plugins/supervisor/add_process.html b/public/panel/views/plugins/supervisor/add_process.html new file mode 100644 index 0000000000..0389c7dc48 --- /dev/null +++ b/public/panel/views/plugins/supervisor/add_process.html @@ -0,0 +1,94 @@ + + + diff --git a/routes/plugin.go b/routes/plugin.go index d3d8e6857c..567e03364f 100644 --- a/routes/plugin.go +++ b/routes/plugin.go @@ -3,6 +3,7 @@ package routes import ( "github.com/goravel/framework/contracts/route" "github.com/goravel/framework/facades" + "panel/app/http/controllers/plugins/supervisor" "panel/app/http/controllers/plugins/mysql57" "panel/app/http/controllers/plugins/mysql80" @@ -136,4 +137,27 @@ func Plugin() { route.Post("add", s3fsController.Add) route.Post("delete", s3fsController.Delete) }) + facades.Route().Prefix("api/plugins/supervisor").Middleware(middleware.Jwt()).Group(func(route route.Route) { + supervisorController := supervisor.NewSupervisorController() + route.Get("status", supervisorController.Status) + route.Post("start", supervisorController.Start) + route.Post("stop", supervisorController.Stop) + route.Post("restart", supervisorController.Restart) + route.Post("reload", supervisorController.Reload) + route.Get("log", supervisorController.Log) + route.Post("clearLog", supervisorController.ClearLog) + route.Get("config", supervisorController.Config) + route.Post("config", supervisorController.SaveConfig) + route.Get("processes", supervisorController.Processes) + route.Post("startProcess", supervisorController.StartProcess) + route.Post("stopProcess", supervisorController.StopProcess) + route.Post("restartProcess", supervisorController.RestartProcess) + route.Get("processLog", supervisorController.ProcessLog) + route.Post("clearProcessLog", supervisorController.ClearProcessLog) + route.Get("processConfig", supervisorController.ProcessConfig) + route.Post("processConfig", supervisorController.SaveProcessConfig) + route.Post("deleteProcess", supervisorController.DeleteProcess) + route.Post("addProcess", supervisorController.AddProcess) + + }) } diff --git a/scripts/supervisor/install.sh b/scripts/supervisor/install.sh new file mode 100644 index 0000000000..ca9f8aad23 --- /dev/null +++ b/scripts/supervisor/install.sh @@ -0,0 +1,38 @@ +#!/bin/bash +export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH + +: ' +Copyright 2022 HaoZi Technology Co., Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +' + +HR="+----------------------------------------------------" +OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown") + +if [ "${OS}" == "centos" ]; then + dnf install -y supervisor + sed -i 's#files = supervisord.d/\*.ini#files = supervisord.d/*.conf#g' /etc/supervisord.conf + systemctl enable supervisord + systemctl start supervisord +elif [ "${OS}" == "debian" ]; then + apt install -y supervisor + systemctl enable supervisor + systemctl start supervisor +else + echo -e $HR + echo "错误:不支持的操作系统" + exit 1 +fi + +panel writePlugin supervisor 4.2.5 diff --git a/scripts/supervisor/uninstall.sh b/scripts/supervisor/uninstall.sh new file mode 100644 index 0000000000..0b990a468e --- /dev/null +++ b/scripts/supervisor/uninstall.sh @@ -0,0 +1,33 @@ +#!/bin/bash +export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH + +: ' +Copyright 2022 HaoZi Technology Co., Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +' + +HR="+----------------------------------------------------" +OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown") + +if [ "${OS}" == "centos" ]; then + dnf remove -y supervisor +elif [ "${OS}" == "debian" ]; then + apt remove -y supervisor +else + echo -e $HR + echo "错误:不支持的操作系统" + exit 1 +fi + +panel deletePlugin supervisor