From 0d4f8bbea1c532d6ecc1f0f30182bee4b6748df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Fri, 21 Jul 2023 02:26:12 +0800 Subject: [PATCH] feat: update --- app/console/commands/panel.go | 11 +- app/http/controllers/info_controller.go | 39 +++++ .../plugins/openresty/openresty_controller.go | 67 +++++--- app/http/controllers/task_controller.go | 2 +- config/panel.go | 2 +- pkg/tools/system.go | 6 +- pkg/tools/tools.go | 47 +++++- public/panel/adminui/src/modules/view.js | 2 +- public/panel/views/index.html | 60 +++++-- public/panel/views/plugins/openresty.html | 83 +++++---- public/panel/views/task.html | 158 ++++++++++++++++++ routes/plugin.go | 2 +- routes/web.go | 2 + scripts/install_panel.sh | 3 +- scripts/openresty/install.sh | 2 +- 15 files changed, 388 insertions(+), 98 deletions(-) create mode 100644 public/panel/views/task.html diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index 0c5149fe05..4e02d25d97 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -73,7 +73,12 @@ func (receiver *Panel) Handle(ctx console.Context) error { color.Greenln("初始化成功") case "update": - err := tools.UpdatePanel() + input := arg1 + proxy := false + if input == "y" || input == "Y" || input == "yes" || input == "Yes" { + proxy = true + } + err := tools.UpdatePanel(cast.ToBool(proxy)) if err != nil { color.Redln("更新失败: " + err.Error()) return nil @@ -279,7 +284,7 @@ func (receiver *Panel) Handle(ctx console.Context) error { default: color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version")) color.Greenln("请使用以下命令:") - color.Greenln("panel update 更新/修复面板到最新版本") + color.Greenln("panel update {proxy} 更新/修复面板到最新版本") color.Greenln("panel getInfo 重新初始化面板账号信息") color.Greenln("panel getPort 获取面板访问端口") color.Greenln("panel getEntrance 获取面板访问入口") @@ -287,7 +292,7 @@ func (receiver *Panel) Handle(ctx console.Context) error { color.Greenln("panel backup {website/mysql/postgresql} {name} {path} 备份网站/MySQL数据库/PostgreSQL数据库到指定目录") color.Redln("以下命令请在开发者指导下使用:") color.Yellowln("panel init 初始化面板") - color.Yellowln("panel writePlugin {slug} 写入插件安装状态") + color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态") color.Yellowln("panel deletePlugin {slug} 移除插件安装状态") color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码") color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板") diff --git a/app/http/controllers/info_controller.go b/app/http/controllers/info_controller.go index f546937077..9f69b713d7 100644 --- a/app/http/controllers/info_controller.go +++ b/app/http/controllers/info_controller.go @@ -122,3 +122,42 @@ func (r *InfoController) InstalledDbAndPhp(ctx http.Context) { "postgresql": postgresqlInstalled, }) } + +func (r *InfoController) CheckUpdate(ctx http.Context) { + version := facades.Config().GetString("panel.version") + remote, err := tools.GetLatestPanelVersion() + if err != nil { + Error(ctx, http.StatusInternalServerError, "获取最新版本失败") + return + } + + if version == remote.Version { + Success(ctx, http.Json{ + "update": false, + "version": remote.Version, + "name": remote.Name, + "body": remote.Body, + "date": remote.Date, + }) + return + } + + Success(ctx, http.Json{ + "update": true, + "version": remote.Version, + "name": remote.Name, + "body": remote.Body, + "date": remote.Date, + }) +} + +func (r *InfoController) Update(ctx http.Context) { + proxy := ctx.Request().InputBool("proxy") + err := tools.UpdatePanel(proxy) + if err != nil { + Error(ctx, http.StatusInternalServerError, "更新失败") + return + } + + Success(ctx, nil) +} diff --git a/app/http/controllers/plugins/openresty/openresty_controller.go b/app/http/controllers/plugins/openresty/openresty_controller.go index bc3aba8475..aded1244a4 100644 --- a/app/http/controllers/plugins/openresty/openresty_controller.go +++ b/app/http/controllers/plugins/openresty/openresty_controller.go @@ -170,6 +170,11 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) { return } + if !tools.Exists("/www/wwwlogs/nginx_error.log") { + controllers.Success(ctx, "") + return + } + out := tools.ExecShell("tail -n 100 /www/wwwlogs/nginx_error.log") controllers.Success(ctx, out) } @@ -180,7 +185,7 @@ func (r *OpenRestyController) ClearErrorLog(ctx http.Context) { return } - _ = tools.ExecShell("echo '' > /www/wwwlogs/nginx_error.log") + tools.ExecShell("echo '' > /www/wwwlogs/nginx_error.log") controllers.Success(ctx, "清空OpenResty错误日志成功") } @@ -199,42 +204,64 @@ func (r *OpenRestyController) Load(ctx http.Context) { } raw := resp.String() - var data map[int]map[string]any + type nginxStatus struct { + Name string `json:"name"` + Value string `json:"value"` + } + var data []nginxStatus out := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | wc -l") workers := strings.TrimSpace(out) - data[0]["name"] = "工作进程" - data[0]["value"] = workers + data = append(data, nginxStatus{ + Name: "工作进程", + Value: workers, + }) out = tools.ExecShell("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'") mem := tools.FormatBytes(cast.ToFloat64(strings.TrimSpace(out))) - data[1]["name"] = "内存占用" - data[1]["value"] = mem + data = append(data, nginxStatus{ + Name: "内存占用", + Value: mem, + }) match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 2 { - data[2]["name"] = "活跃连接数" - data[2]["value"] = match[1] + data = append(data, nginxStatus{ + Name: "活跃连接数", + Value: match[1], + }) } match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 4 { - data[3]["name"] = "总连接次数" - data[3]["value"] = match[1] - data[4]["name"] = "总握手次数" - data[4]["value"] = match[2] - data[5]["name"] = "总请求次数" - data[5]["value"] = match[3] + data = append(data, nginxStatus{ + Name: "总连接次数", + Value: match[1], + }) + data = append(data, nginxStatus{ + Name: "总握手次数", + Value: match[2], + }) + data = append(data, nginxStatus{ + Name: "总请求次数", + Value: match[3], + }) } match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 4 { - data[6]["name"] = "请求数" - data[6]["value"] = match[1] - data[7]["name"] = "响应数" - data[7]["value"] = match[2] - data[8]["name"] = "驻留进程" - data[8]["value"] = match[3] + data = append(data, nginxStatus{ + Name: "请求数", + Value: match[1], + }) + data = append(data, nginxStatus{ + Name: "响应数", + Value: match[2], + }) + data = append(data, nginxStatus{ + Name: "驻留进程", + Value: match[3], + }) } controllers.Success(ctx, data) diff --git a/app/http/controllers/task_controller.go b/app/http/controllers/task_controller.go index 13bb4c74bc..275ec52f09 100644 --- a/app/http/controllers/task_controller.go +++ b/app/http/controllers/task_controller.go @@ -69,7 +69,7 @@ func (r *TaskController) Log(ctx http.Context) { func (r *TaskController) Delete(ctx http.Context) { var task models.Task - _, err := facades.Orm().Query().Where("id", ctx.Request().QueryInt("id")).Delete(&task) + _, err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).Delete(&task) if err != nil { facades.Log().Error("[面板][TaskController] 删除任务失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") diff --git a/config/panel.go b/config/panel.go index 4d0d9d674e..74e305f004 100644 --- a/config/panel.go +++ b/config/panel.go @@ -8,6 +8,6 @@ func init() { config := facades.Config() config.Add("panel", map[string]any{ "name": "耗子面板", - "version": "2.0.0", + "version": "v2.0.0", }) } diff --git a/pkg/tools/system.go b/pkg/tools/system.go index 1fc55b425e..cf7bf60167 100644 --- a/pkg/tools/system.go +++ b/pkg/tools/system.go @@ -51,7 +51,7 @@ func ExecShell(shell string) string { output, err := cmd.CombinedOutput() if err != nil { - facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error()) + facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error()) return "" } @@ -64,13 +64,13 @@ func ExecShellAsync(shell string) { err := cmd.Start() if err != nil { - facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error()) + facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error()) } go func() { err := cmd.Wait() if err != nil { - facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error()) + facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error()) } }() } diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 67d1ea2ba7..7aba572ae8 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -5,9 +5,11 @@ import ( "errors" "os" "os/exec" + "strconv" "strings" "time" + "github.com/gookit/color" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/host" @@ -114,17 +116,52 @@ func GetLatestPanelVersion() (PanelInfo, error) { } // UpdatePanel 更新面板 -func UpdatePanel() error { +func UpdatePanel(proxy bool) error { panelInfo, err := GetLatestPanelVersion() if err != nil { return err } - cmd := exec.Command("/bin/bash", "-c", "wget -O panel.tar.gz "+panelInfo.DownloadUrl+" && tar -zxvf panel.tar.gz && rm -rf panel.tar.gz && chmod +x panel && ./panel artisan migrate") - _, err = cmd.Output() - if err != nil { - return errors.New("更新面板失败") + color.Greenln("最新版本: " + panelInfo.Version) + color.Greenln("下载链接: " + panelInfo.DownloadUrl) + color.Greenln("使用代理: " + strconv.FormatBool(proxy)) + + color.Greenln("备份面板配置...") + ExecShell("cp -f /www/panel/database/panel.db /tmp/panel.db.bak") + ExecShell("cp -f /www/panel/panel.conf /tmp/panel.conf.bak") + if !Exists("/tmp/panel.db.bak") || !Exists("/tmp/panel.conf.bak") { + return errors.New("备份面板配置失败") + } + color.Greenln("备份完成") + + color.Greenln("清理旧版本...") + ExecShell("rm -rf /www/panel/*") + color.Greenln("清理完成") + + color.Greenln("正在下载...") + if proxy { + ExecShell("wget -O /www/panel/panel.zip https://ghproxy.com/" + panelInfo.DownloadUrl) + } else { + ExecShell("wget -O /www/panel/panel.zip " + panelInfo.DownloadUrl) } + color.Greenln("下载完成") + + color.Greenln("更新新版本...") + ExecShell("cd /www/panel && unzip -o panel.zip && rm -rf panel.zip && chmod 700 panel") + color.Greenln("更新完成") + + color.Greenln("恢复面板配置...") + ExecShell("cp -f /tmp/panel.db.bak /www/panel/database/panel.db") + ExecShell("cp -f /tmp/panel.conf.bak /www/panel/panel.conf") + if !Exists("/www/panel/database/panel.db") || !Exists("/www/panel/panel.conf") { + return errors.New("恢复面板配置失败") + } + ExecShell("/www/panel/panel --env=panel.conf artisan migrate") + color.Greenln("恢复完成") + + color.Greenln("重启面板...") + ExecShell("systemctl restart panel") + color.Greenln("重启完成") return nil } diff --git a/public/panel/adminui/src/modules/view.js b/public/panel/adminui/src/modules/view.js index def1b26c20..373f032a53 100644 --- a/public/panel/adminui/src/modules/view.js +++ b/public/panel/adminui/src/modules/view.js @@ -63,7 +63,7 @@ layui.define(['laytpl', 'layer'], function (exports) { delete options.success delete options.error - if (options.type === 'post' || options.type === 'put' || options.type === 'delete' || options.type === 'patch' || options.type === 'POST' || options.type === 'PUT' || options.type === 'DELETE' || options.type === 'PATCH') { + if (options.method === 'post' || options.method === 'put' || options.method === 'delete' || options.method === 'patch' || options.method === 'POST' || options.method === 'PUT' || options.method === 'DELETE' || options.method === 'PATCH') { options.contentType = 'application/json' options.data = JSON.stringify(options.data) } diff --git a/public/panel/views/index.html b/public/panel/views/index.html index 4cc2a3aa86..83d57e574e 100644 --- a/public/panel/views/index.html +++ b/public/panel/views/index.html @@ -320,22 +320,50 @@

{{index}}

layer.msg('获取版本信息失败,请刷新重试!') return false } - if (result.data.version) { - admin.popup({ - title: '提示' - , - shade: 0 - , - anim: -1 - , - area: ['400px', '200px'] - , - id: 'layadmin-layer-skin-update-panel' - , - skin: 'layui-anim layui-anim-upbit' - , - content: '最新版本:' + result.data.version + '

更新日志:' + result.data.describe + '

请在SSH执行panel update以更新面板!' - }) + if (result.data.update) { + layer.confirm('更新日期:
'+new Date(result.data.date).toLocaleString()+'
更新日志:
'+result.data.body+'
', { + title: '最新版本: '+result.data.version+' ,是否更新?', + btn: ['更新', '取消'] + }, function () { + let proxy = false + layer.confirm('对于大陆服务器,建议使用代理进行更新', { + title: '是否使用代理更新?', + btn: ['是', '否'] + }, function () { + proxy = true + index = layer.msg('正在更新...', { icon: 16, time: 0, shade: 0.3 }) + admin.req({ + url: '/api/panel/info/update' + , method: 'post' + , data: { proxy: proxy } + , success: function (result) { + layer.close(index) + if (result.code !== 0) { + return false + } + layer.alert('更新成功!') + location.href = '/'; + } + }) + }, function(){ + proxy = false + index = layer.msg('正在更新...', { icon: 16, time: 0, shade: 0.3 }) + admin.req({ + url: '/api/panel/info/update' + , method: 'post' + , data: { proxy: proxy } + , success: function (result) { + layer.close(index) + if (result.code !== 0) { + return false + } + layer.alert('更新成功!') + location.href = '/'; + } + }) + }) + + }) } else { layer.msg('当前已是最新版本!') } diff --git a/public/panel/views/plugins/openresty.html b/public/panel/views/plugins/openresty.html index 0b16f17403..4a13191a16 100644 --- a/public/panel/views/plugins/openresty.html +++ b/public/panel/views/plugins/openresty.html @@ -43,7 +43,7 @@
- +
                                     获取中...
@@ -141,54 +141,50 @@
 
     // 事件监听
     $('#openresty-start').click(function () {
-      admin.popup({
-        title: '警告'
-        ,
-        shade: 0
-        ,
-        anim: -1
-        ,
-        area: ['300px', '200px']
-        ,
-        id: 'layadmin-layer-skin-openresty-start'
-        ,
-        skin: 'layui-anim layui-anim-upbit'
-        ,
-        content: '面板的正常访问依赖OpenResty,因此不支持在面板启动OpenResty,如您确需操作,请在SSH执行systemctl start nginx以启动OpenResty!'
-      })
+        index = layer.msg('正在启动OpenResty...', { icon: 16, time: 0, shade: 0.3 })
+        admin.req({
+            url: '/api/plugins/openresty/start'
+            , method: 'post'
+            , success: function (result) {
+                layer.close(index)
+                if (result.code !== 0) {
+                    return false
+                }
+                admin.events.refresh()
+                layer.alert('OpenResty启动成功!')
+            }
+        })
     })
     $('#openresty-stop').click(function () {
-      admin.popup({
-        title: '警告'
-        ,
-        shade: 0
-        ,
-        anim: -1
-        ,
-        area: ['300px', '200px']
-        ,
-        id: 'layadmin-layer-skin-openresty-stop'
-        ,
-        skin: 'layui-anim layui-anim-upbit'
-        ,
-        content: '面板的正常访问依赖OpenResty,因此不支持在面板停止OpenResty,如您确需操作,请在SSH执行systemctl stop nginx以停止OpenResty!'
-      })
+        layer.confirm('停止OpenResty将导致网站无法访问,是否继续停止?', {
+            btn: ['停止', '取消']
+        }, function () {
+            index = layer.msg('正在停止OpenResty...', { icon: 16, time: 0, shade: 0.3 })
+            admin.req({
+                url: '/api/plugins/openresty/stop'
+                , method: 'post'
+                , success: function (result) {
+                    layer.close(index)
+                    if (result.code !== 0) {
+                        return false
+                    }
+                    admin.events.refresh()
+                    layer.alert('OpenResty停止成功!')
+                }
+            })
+        })
     })
     $('#openresty-restart').click(function () {
-      layer.confirm('重启OpenResty有可能导致面板短时间无法访问,是否继续重启?', {
+      layer.confirm('重启OpenResty将导致网站短时间无法访问,是否继续重启?', {
         btn: ['重启', '取消']
       }, function () {
-        index = layer.msg('正在重启OpenResty...', { icon: 16, time: 0 })
+        index = layer.msg('正在重启OpenResty...', { icon: 16, time: 0, shade: 0.3 })
         admin.req({
           url: '/api/plugins/openresty/restart'
           , method: 'post'
-          , beforeSend: function () {
-            layer.msg('已发送重启请求,请稍后刷新确认重启状态。')
-          }
           , success: function (result) {
             layer.close(index)
             if (result.code !== 0) {
-              console.log('耗子Linux面板:OpenResty重启失败,接口返回' + result)
               return false
             }
             admin.events.refresh()
@@ -198,22 +194,22 @@
       })
     })
     $('#openresty-reload').click(function () {
-      index = layer.msg('正在重载OpenResty...', { icon: 16, time: 0 })
+      index = layer.msg('正在重载OpenResty...', { icon: 16, time: 0, shade: 0.3 })
       admin.req({
         url: '/api/plugins/openresty/reload'
         , method: 'post'
         , success: function (result) {
           layer.close(index)
           if (result.code !== 0) {
-            console.log('耗子Linux面板:OpenResty重载失败,接口返回' + result)
             return false
           }
+          admin.events.refresh()
           layer.alert('OpenResty重载成功!')
         }
       })
     })
     $('#openresty-config-save').click(function () {
-      index = layer.msg('正在保存OpenResty主配置...', { icon: 16, time: 0 })
+      index = layer.msg('正在保存OpenResty主配置...', { icon: 16, time: 0, shade: 0.3 })
       admin.req({
         url: '/api/plugins/openresty/config'
         , method: 'post'
@@ -223,17 +219,16 @@
         , success: function (result) {
           layer.close(index)
           if (result.code !== 0) {
-            console.log('耗子Linux面板:OpenResty配置保存失败,接口返回' + result)
             return false
           }
           layer.alert('OpenResty配置保存成功!')
         }
       })
     })
-    $('#openresty-clean-error-log').click(function () {
-      index = layer.msg('正在清空OpenResty错误日志...', { icon: 16, time: 0 })
+    $('#openresty-clear-error-log').click(function () {
+      index = layer.msg('正在清空OpenResty错误日志...', { icon: 16, time: 0, shade: 0.3 })
       admin.req({
-        url: '/api/plugins/openresty/cleanErrorLog'
+        url: '/api/plugins/openresty/clearErrorLog'
         , method: 'post'
         , success: function (result) {
           layer.close(index)
diff --git a/public/panel/views/task.html b/public/panel/views/task.html
new file mode 100644
index 0000000000..f41d4918cb
--- /dev/null
+++ b/public/panel/views/task.html
@@ -0,0 +1,158 @@
+
+
+任务中心
+
+
+
+
+
任务列表
+
+
+
    +
  • 进行中
  • +
  • 等待中
  • +
  • 已完成
  • +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + diff --git a/routes/plugin.go b/routes/plugin.go index 70318f9c50..413a9717c8 100644 --- a/routes/plugin.go +++ b/routes/plugin.go @@ -23,7 +23,7 @@ func Plugin() { route.Get("config", openRestyController.GetConfig) route.Post("config", openRestyController.SaveConfig) route.Get("errorLog", openRestyController.ErrorLog) - route.Get("clearErrorLog", openRestyController.ClearErrorLog) + route.Post("clearErrorLog", openRestyController.ClearErrorLog) }) facades.Route().Prefix("api/plugins/mysql80").Middleware(middleware.Jwt()).Group(func(route route.Route) { mysql80Controller := mysql80.NewMysql80Controller() diff --git a/routes/web.go b/routes/web.go index 8917bc1f75..db7246a919 100644 --- a/routes/web.go +++ b/routes/web.go @@ -19,6 +19,8 @@ func Web() { r.Middleware(middleware.Jwt()).Get("nowMonitor", infoController.NowMonitor) r.Middleware(middleware.Jwt()).Get("systemInfo", infoController.SystemInfo) r.Middleware(middleware.Jwt()).Get("installedDbAndPhp", infoController.InstalledDbAndPhp) + r.Middleware(middleware.Jwt()).Get("checkUpdate", infoController.CheckUpdate) + r.Middleware(middleware.Jwt()).Post("update", infoController.Update) }) r.Prefix("user").Group(func(r route.Route) { userController := controllers.NewUserController() diff --git a/scripts/install_panel.sh b/scripts/install_panel.sh index c6ee700b68..ef0486c0ec 100644 --- a/scripts/install_panel.sh +++ b/scripts/install_panel.sh @@ -58,8 +58,7 @@ Prepare_system() { exit 1 fi - wwwUserCheck=$(cat /etc/passwd | grep www) - if [ "${wwwUserCheck}" == "" ]; then + if ! id -u "www" >/dev/null 2>&1; then groupadd www useradd -s /sbin/nologin -g www www fi diff --git a/scripts/openresty/install.sh b/scripts/openresty/install.sh index fa30ad1c64..7ca0c78fa7 100644 --- a/scripts/openresty/install.sh +++ b/scripts/openresty/install.sh @@ -362,6 +362,6 @@ systemctl daemon-reload systemctl enable openresty.service systemctl start openresty.service -panel writePlugin openresty +panel writePlugin openresty ${openrestyVersion} echo -e "${HR}\nOpenResty install completed.\n${HR}"