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}"