diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index 4e02d25d..02cde4c5 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -50,7 +50,7 @@ func (receiver *Panel) Handle(ctx console.Context) error { return nil } - settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyPanelEntrance, Value: "/"}} + settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyEntrance, Value: "/"}} err = facades.Orm().Query().Create(&settings) if err != nil { color.Redln("初始化失败") @@ -115,14 +115,14 @@ func (receiver *Panel) Handle(ctx console.Context) error { color.Greenln("用户名: " + user.Username) color.Greenln("密码: " + password) color.Greenln("面板端口: " + port) - color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/")) + color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyEntrance, "/")) case "getPort": port := tools.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'") color.Greenln("面板端口: " + port) case "getEntrance": - color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/")) + color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyEntrance, "/")) case "writePlugin": slug := arg1 diff --git a/app/http/controllers/cron_controller.go b/app/http/controllers/cron_controller.go index 360a604d..30a6c5a0 100644 --- a/app/http/controllers/cron_controller.go +++ b/app/http/controllers/cron_controller.go @@ -1,6 +1,7 @@ package controllers import ( + "regexp" "strconv" "github.com/goravel/framework/contracts/http" @@ -44,7 +45,7 @@ func (r *CronController) List(ctx http.Context) { func (r *CronController) Add(ctx http.Context) { validator, err := ctx.Request().Validate(map[string]string{ "name": "required|min_len:1|max_len:255", - "time": "required|regex:^((\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+)(\\,(\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+))*\\s?){5}$", + "time": "required", "script": "required", }) if err != nil { @@ -56,6 +57,12 @@ func (r *CronController) Add(ctx http.Context) { return } + // 单独验证时间格式 + if !regexp.MustCompile(`^((\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+)(,(\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+))*\s?){5}$`).MatchString(ctx.Request().Input("time")) { + Error(ctx, http.StatusBadRequest, "时间格式错误") + return + } + // 写入shell shellDir := "/www/server/cron/" shellLogDir := "/www/server/cron/logs/" @@ -70,7 +77,7 @@ func (r *CronController) Add(ctx http.Context) { return } shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + tools.RandomString(16) - if !tools.WriteFile(shellDir+shellFile+".sh", ctx.Request().Input("script"), 0644) { + if !tools.WriteFile(shellDir+shellFile+".sh", ctx.Request().Input("script"), 0700) { facades.Log().Error("[面板][CronController] 创建计划任务脚本失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") return @@ -99,11 +106,22 @@ func (r *CronController) Add(ctx http.Context) { }) } +// Script 获取脚本内容 +func (r *CronController) Script(ctx http.Context) { + var cron models.Cron + err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) + if err != nil { + Error(ctx, http.StatusBadRequest, "计划任务不存在") + return + } + + Success(ctx, tools.ReadFile(cron.Shell)) +} + func (r *CronController) Update(ctx http.Context) { validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", "name": "required|min_len:1|max_len:255", - "time": "required|regex:^((\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+)(\\,(\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+))*\\s?){5}$", + "time": "required", "script": "required", }) if err != nil { @@ -115,6 +133,12 @@ func (r *CronController) Update(ctx http.Context) { return } + // 单独验证时间格式 + if !regexp.MustCompile(`^((\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+)(,(\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+))*\s?){5}$`).MatchString(ctx.Request().Input("time")) { + Error(ctx, http.StatusBadRequest, "时间格式错误") + return + } + var cron models.Cron err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) if err != nil { @@ -122,6 +146,20 @@ func (r *CronController) Update(ctx http.Context) { return } + if !cron.Status { + Error(ctx, http.StatusBadRequest, "计划任务已禁用") + return + } + + cron.Time = ctx.Request().Input("time") + cron.Name = ctx.Request().Input("name") + err = facades.Orm().Query().Save(&cron) + if err != nil { + facades.Log().Error("[面板][CronController] 更新计划任务失败 ", err) + Error(ctx, http.StatusInternalServerError, "系统内部错误") + return + } + if !tools.WriteFile(cron.Shell, ctx.Request().Input("script"), 0644) { facades.Log().Error("[面板][CronController] 更新计划任务脚本失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") @@ -138,25 +176,16 @@ func (r *CronController) Update(ctx http.Context) { } func (r *CronController) Delete(ctx http.Context) { - validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", - }) - if err != nil { - Error(ctx, http.StatusBadRequest, err.Error()) - return - } - if validator.Fails() { - Error(ctx, http.StatusBadRequest, validator.Errors().All()) - return - } - var cron models.Cron - err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) + err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) if err != nil { Error(ctx, http.StatusBadRequest, "计划任务不存在") return } + r.cron.DeleteFromSystem(cron) + tools.RemoveFile(cron.Shell) + _, err = facades.Orm().Query().Delete(&cron) if err != nil { facades.Log().Error("[面板][CronController] 删除计划任务失败 ", err) @@ -164,22 +193,19 @@ func (r *CronController) Delete(ctx http.Context) { return } - r.cron.DeleteFromSystem(cron) - Success(ctx, nil) } func (r *CronController) Status(ctx http.Context) { validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", - "status": "required|in:true,false", + "status": "bool", }) if err != nil { Error(ctx, http.StatusBadRequest, err.Error()) return } if validator.Fails() { - Error(ctx, http.StatusBadRequest, validator.Errors().All()) + Error(ctx, http.StatusBadRequest, validator.Errors().One()) return } @@ -191,7 +217,7 @@ func (r *CronController) Status(ctx http.Context) { } cron.Status = ctx.Request().InputBool("status") - _, err = facades.Orm().Query().Update(&cron) + err = facades.Orm().Query().Save(&cron) if err != nil { facades.Log().Error("[面板][CronController] 更新计划任务状态失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") @@ -207,20 +233,8 @@ func (r *CronController) Status(ctx http.Context) { } func (r *CronController) Log(ctx http.Context) { - validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", - }) - if err != nil { - Error(ctx, http.StatusBadRequest, err.Error()) - return - } - if validator.Fails() { - Error(ctx, http.StatusBadRequest, validator.Errors().All()) - return - } - var cron models.Cron - err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) + err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron) if err != nil { Error(ctx, http.StatusBadRequest, "计划任务不存在") return @@ -231,7 +245,5 @@ func (r *CronController) Log(ctx http.Context) { return } - Success(ctx, http.Json{ - "log": tools.ReadFile(cron.Log), - }) + Success(ctx, tools.ReadFile(cron.Log)) } diff --git a/app/http/controllers/plugins/mysql80/mysql80_controller.go b/app/http/controllers/plugins/mysql80/mysql80_controller.go index 34f82868..18f1e3b6 100644 --- a/app/http/controllers/plugins/mysql80/mysql80_controller.go +++ b/app/http/controllers/plugins/mysql80/mysql80_controller.go @@ -31,7 +31,7 @@ func NewMysql80Controller() *Mysql80Controller { } // Status 获取运行状态 -func (r *Mysql80Controller) Status(ctx http.Context) { +func (c *Mysql80Controller) Status(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -51,7 +51,7 @@ func (r *Mysql80Controller) Status(ctx http.Context) { } // Reload 重载配置 -func (r *Mysql80Controller) Reload(ctx http.Context) { +func (c *Mysql80Controller) Reload(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -72,7 +72,7 @@ func (r *Mysql80Controller) Reload(ctx http.Context) { } // Restart 重启服务 -func (r *Mysql80Controller) Restart(ctx http.Context) { +func (c *Mysql80Controller) Restart(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -93,7 +93,7 @@ func (r *Mysql80Controller) Restart(ctx http.Context) { } // Start 启动服务 -func (r *Mysql80Controller) Start(ctx http.Context) { +func (c *Mysql80Controller) Start(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -114,7 +114,7 @@ func (r *Mysql80Controller) Start(ctx http.Context) { } // Stop 停止服务 -func (r *Mysql80Controller) Stop(ctx http.Context) { +func (c *Mysql80Controller) Stop(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -135,7 +135,7 @@ func (r *Mysql80Controller) Stop(ctx http.Context) { } // GetConfig 获取配置 -func (r *Mysql80Controller) GetConfig(ctx http.Context) { +func (c *Mysql80Controller) GetConfig(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -151,7 +151,7 @@ func (r *Mysql80Controller) GetConfig(ctx http.Context) { } // SaveConfig 保存配置 -func (r *Mysql80Controller) SaveConfig(ctx http.Context) { +func (c *Mysql80Controller) SaveConfig(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -171,12 +171,12 @@ func (r *Mysql80Controller) SaveConfig(ctx http.Context) { } // Load 获取负载 -func (r *Mysql80Controller) Load(ctx http.Context) { +func (c *Mysql80Controller) Load(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) if len(rootPassword) == 0 { controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空") return @@ -249,7 +249,7 @@ func (r *Mysql80Controller) Load(ctx http.Context) { } // ErrorLog 获取错误日志 -func (r *Mysql80Controller) ErrorLog(ctx http.Context) { +func (c *Mysql80Controller) ErrorLog(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -259,7 +259,7 @@ func (r *Mysql80Controller) ErrorLog(ctx http.Context) { } // ClearErrorLog 清空错误日志 -func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) { +func (c *Mysql80Controller) ClearErrorLog(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -269,7 +269,7 @@ func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) { } // SlowLog 获取慢查询日志 -func (r *Mysql80Controller) SlowLog(ctx http.Context) { +func (c *Mysql80Controller) SlowLog(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -279,7 +279,7 @@ func (r *Mysql80Controller) SlowLog(ctx http.Context) { } // ClearSlowLog 清空慢查询日志 -func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) { +func (c *Mysql80Controller) ClearSlowLog(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -289,12 +289,12 @@ func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) { } // GetRootPassword 获取root密码 -func (r *Mysql80Controller) GetRootPassword(ctx http.Context) { +func (c *Mysql80Controller) GetRootPassword(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) if len(rootPassword) == 0 { controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空") return @@ -304,7 +304,7 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) { } // SetRootPassword 设置root密码 -func (r *Mysql80Controller) SetRootPassword(ctx http.Context) { +func (c *Mysql80Controller) SetRootPassword(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -326,11 +326,11 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) { return } - oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + oldRootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) if oldRootPassword != rootPassword { tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"") tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"") - err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword) + err := c.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword) if err != nil { tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"") tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") @@ -343,12 +343,12 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) { } // DatabaseList 获取数据库列表 -func (r *Mysql80Controller) DatabaseList(ctx http.Context) { +func (c *Mysql80Controller) DatabaseList(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } - out := tools.ExecShell("mysql -uroot -p" + r.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"") + out := tools.ExecShell("mysql -uroot -p" + c.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"") databases := strings.Split(out, "\n") databases = databases[1 : len(databases)-1] @@ -374,7 +374,7 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) { } // AddDatabase 添加数据库 -func (r *Mysql80Controller) AddDatabase(ctx http.Context) { +func (c *Mysql80Controller) AddDatabase(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -393,7 +393,7 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") user := ctx.Request().Input("user") password := ctx.Request().Input("password") @@ -407,7 +407,7 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) { } // DeleteDatabase 删除数据库 -func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) { +func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -424,7 +424,7 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"") @@ -432,8 +432,8 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) { } // BackupList 获取备份列表 -func (r *Mysql80Controller) BackupList(ctx http.Context) { - backupPath := "/www/backup/mysql" +func (c *Mysql80Controller) BackupList(ctx http.Context) { + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" if !tools.Exists(backupPath) { tools.Mkdir(backupPath, 0644) @@ -466,7 +466,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) { } // CreateBackup 创建备份 -func (r *Mysql80Controller) CreateBackup(ctx http.Context) { +func (c *Mysql80Controller) CreateBackup(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -483,8 +483,8 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) { return } - backupPath := "/www/backup/mysql" - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") backupFile := backupPath + "/" + database + "_" + carbon.Now().ToShortDateTimeString() + ".sql" if !tools.Exists(backupPath) { @@ -498,7 +498,7 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) { } tools.ExecShell("mysqldump -uroot " + database + " > " + backupFile) - tools.ExecShell("zip -r " + backupFile + ".zip " + backupFile) + tools.ExecShell("zip -c " + backupFile + ".zip " + backupFile) tools.RemoveFile(backupFile) _ = os.Unsetenv("MYSQL_PWD") @@ -506,7 +506,7 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) { } // DeleteBackup 删除备份 -func (r *Mysql80Controller) DeleteBackup(ctx http.Context) { +func (c *Mysql80Controller) DeleteBackup(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -523,7 +523,7 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) { return } - backupPath := "/www/backup/mysql" + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" file := ctx.Request().Input("file") tools.RemoveFile(backupPath + "/" + file) @@ -531,7 +531,7 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) { } // RestoreBackup 还原备份 -func (r *Mysql80Controller) RestoreBackup(ctx http.Context) { +func (c *Mysql80Controller) RestoreBackup(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -549,8 +549,8 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) { return } - backupPath := "/www/backup/mysql" - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) file := ctx.Request().Input("file") backupFile := backupPath + "/" + file if !tools.Exists(backupFile) { @@ -604,7 +604,7 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) { } // UserList 用户列表 -func (r *Mysql80Controller) UserList(ctx http.Context) { +func (c *Mysql80Controller) UserList(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -615,7 +615,7 @@ func (r *Mysql80Controller) UserList(ctx http.Context) { Privileges string `json:"privileges"` } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'select user,host from mysql.user'") rawUsers := strings.Split(out, "\n") users := make([]User, 0) @@ -642,7 +642,7 @@ func (r *Mysql80Controller) UserList(ctx http.Context) { } // AddUser 添加用户 -func (r *Mysql80Controller) AddUser(ctx http.Context) { +func (c *Mysql80Controller) AddUser(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -661,7 +661,7 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") password := ctx.Request().Input("password") database := ctx.Request().Input("database") @@ -673,7 +673,7 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) { } // DeleteUser 删除用户 -func (r *Mysql80Controller) DeleteUser(ctx http.Context) { +func (c *Mysql80Controller) DeleteUser(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -690,7 +690,7 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"") @@ -698,7 +698,7 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) { } // SetUserPassword 设置用户密码 -func (r *Mysql80Controller) SetUserPassword(ctx http.Context) { +func (c *Mysql80Controller) SetUserPassword(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -716,7 +716,7 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") password := ctx.Request().Input("password") tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"") @@ -726,7 +726,7 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) { } // SetUserPrivileges 设置用户权限 -func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) { +func (c *Mysql80Controller) SetUserPrivileges(ctx http.Context) { if !plugins.Check(ctx, "mysql80") { return } @@ -744,7 +744,7 @@ func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) { return } - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") database := ctx.Request().Input("database") tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"") diff --git a/app/http/controllers/setting_controller.go b/app/http/controllers/setting_controller.go index 65f16eba..f4be66f6 100644 --- a/app/http/controllers/setting_controller.go +++ b/app/http/controllers/setting_controller.go @@ -46,10 +46,11 @@ func (r *SettingController) List(ctx http.Context) { return } - result["username"] = user.Username result["email"] = user.Email + result["port"] = tools.ExecShell(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`) + Success(ctx, result) } @@ -58,7 +59,7 @@ func (r *SettingController) Save(ctx http.Context) { port := ctx.Request().Input("port") backupPath := ctx.Request().Input("backup_path") websitePath := ctx.Request().Input("website_path") - panelEntrance := ctx.Request().Input("panel_entrance") + entrance := ctx.Request().Input("entrance") username := ctx.Request().Input("username") email := ctx.Request().Input("email") password := ctx.Request().Input("password") @@ -70,10 +71,13 @@ func (r *SettingController) Save(ctx http.Context) { return } - oldPort := tools.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'") + oldPort := tools.ExecShell(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`) if oldPort != port { tools.ExecShell("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf") } + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } err = r.setting.Set(models.SettingKeyBackupPath, backupPath) if err != nil { facades.Log().Error("[面板][SettingController] 保存设置失败 ", err) @@ -81,6 +85,10 @@ func (r *SettingController) Save(ctx http.Context) { return } + if !tools.Exists(websitePath) { + tools.Mkdir(websitePath, 0755) + tools.Chown(websitePath, "www", "www") + } err = r.setting.Set(models.SettingKeyWebsitePath, websitePath) if err != nil { facades.Log().Error("[面板][SettingController] 保存设置失败 ", err) @@ -88,7 +96,7 @@ func (r *SettingController) Save(ctx http.Context) { return } - err = r.setting.Set(models.SettingKeyPanelEntrance, panelEntrance) + err = r.setting.Set(models.SettingKeyEntrance, entrance) if err != nil { facades.Log().Error("[面板][SettingController] 保存设置失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") @@ -105,8 +113,12 @@ func (r *SettingController) Save(ctx http.Context) { return } - user.Username = username - user.Email = email + if len(username) > 0 { + user.Username = username + } + if len(email) > 0 { + user.Email = email + } if len(password) > 0 { hash, err := facades.Hash().Make(password) if err != nil { diff --git a/app/http/middleware/static.go b/app/http/middleware/static.go index 318037c9..7437c98e 100644 --- a/app/http/middleware/static.go +++ b/app/http/middleware/static.go @@ -11,7 +11,7 @@ import ( func Static() contractshttp.Middleware { return func(ctx contractshttp.Context) { - static.Serve(services.NewSettingImpl().Get("panel_entrance", "/"), static.LocalFile("/www/panel/public", false))(ctx.(*frameworkhttp.GinContext).Instance()) + static.Serve(services.NewSettingImpl().Get("entrance", "/"), static.LocalFile("/www/panel/public", false))(ctx.(*frameworkhttp.GinContext).Instance()) ctx.Request().Next() } diff --git a/app/models/setting.go b/app/models/setting.go index 68c90170..7d9cfa5f 100644 --- a/app/models/setting.go +++ b/app/models/setting.go @@ -8,7 +8,7 @@ const ( SettingKeyMonitorDays = "monitor_days" SettingKeyBackupPath = "backup_path" SettingKeyWebsitePath = "website_path" - SettingKeyPanelEntrance = "panel_entrance" + SettingKeyEntrance = "entrance" SettingKeyMysqlRootPassword = "mysql_root_password" ) diff --git a/app/services/cron.go b/app/services/cron.go index 3f2252ff..6573a42d 100644 --- a/app/services/cron.go +++ b/app/services/cron.go @@ -1,6 +1,8 @@ package services import ( + "strings" + "panel/app/models" "panel/pkg/tools" ) @@ -24,17 +26,15 @@ func (r *CronImpl) AddToSystem(cron models.Cron) { } else { tools.ExecShell("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/crontabs/root") } - - tools.ExecShell("systemctl restart crond") } // DeleteFromSystem 从系统中删除 func (r *CronImpl) DeleteFromSystem(cron models.Cron) { + // 需要转义Shell路径的/为\/ + cron.Shell = strings.ReplaceAll(cron.Shell, "/", "\\/") if tools.IsRHEL() { tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root") } else { tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root") } - - tools.ExecShell("systemctl restart crond") } diff --git a/app/services/website.go b/app/services/website.go index 61fafd6f..f87c67df 100644 --- a/app/services/website.go +++ b/app/services/website.go @@ -61,10 +61,13 @@ type WebsiteSetting struct { } type WebsiteImpl struct { + setting Setting } func NewWebsiteImpl() *WebsiteImpl { - return &WebsiteImpl{} + return &WebsiteImpl{ + setting: NewSettingImpl(), + } } // List 列出网站 @@ -88,7 +91,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) { // path为空时,设置默认值 if len(website.Path) == 0 { - website.Path = "/www/wwwroot/" + website.Name + website.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + website.Name } // path不为/开头时,返回错误 if website.Path[0] != '/' { diff --git a/public/panel/adminui/src/modules/view.js b/public/panel/adminui/src/modules/view.js index 373f032a..def1b26c 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.method === 'post' || options.method === 'put' || options.method === 'delete' || options.method === 'patch' || options.method === 'POST' || options.method === 'PUT' || options.method === 'DELETE' || options.method === 'PATCH') { + 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') { options.contentType = 'application/json' options.data = JSON.stringify(options.data) } diff --git a/public/panel/modules/cron.css b/public/panel/modules/cron.css new file mode 100644 index 00000000..58ee1ed1 --- /dev/null +++ b/public/panel/modules/cron.css @@ -0,0 +1,194 @@ + +/* 样式加载完毕的标识 */ +html #layuicss-cron { + display: none; + position: absolute; + width: 1989px; +} + + +/* 主体结构 */ +.layui-cron { + width: 550px; + position: absolute; + z-index: 99999999; + margin: 5px 0; + border-radius: 2px; + font-size: 14px; + -webkit-animation-duration: 0.3s; + animation-duration: 0.3s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background-color: white; + display: flex; + flex-direction: column; + box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 5px 0px; + -webkit-animation-name: cron-upbit; + animation-name: cron-upbit; + border: 1px solid #e6e6e6; +} + +.layui-cron-main ul { + padding-left: 10px; +} + +@-webkit-keyframes cron-upbit { + + /* 微微往上滑入 */ + from { + -webkit-transform: translate3d(0, 20px, 0); + opacity: 0.3; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes cron-upbit { + from { + transform: translate3d(0, 20px, 0); + opacity: 0.3; + } + + to { + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +/* tabs */ +.layui-cron>.layui-tab { + margin: 0; + box-shadow: none; + border: none; +} + +/* 行 */ +.cron-row { + padding-left: 13px; +} +/* 格 */ +.cron-grid { + padding-left: 15px; +} + +/* 表达式 */ +.cron-title { + font-weight: 700; + font-size: 14px; + margin: 10px; + margin-bottom: 0; +} + +.cron-box { + margin: 10px; +} + +.cron-box+.cron-box { + margin-top: 0; +} + +/* 按钮 */ +.cron-footer-btns { + text-align: right; + margin-right: 10px; + margin-bottom: 10px; +} + +.cron-footer-btns span { + height: 26px; + line-height: 26px; + margin: 0 0 0 -1px; + padding: 0 10px; + border: 1px solid #C9C9C9; + background-color: #fff; + white-space: nowrap; + vertical-align: top; + border-radius: 2px; + display: inline-block; + cursor: pointer; + font-size: 12px; + box-sizing: border-box; + color: #666; +} + +.cron-footer-btns span:hover { + color: #5FB878; +} + + +/* 表单 */ +.layui-cron .layui-form-radio { + margin-right: 0; +} + +.cron-form { + line-height: 28px; + font-size: 14px; +} + +.cron-input-mid { + display: inline-block; + vertical-align: middle; + margin-top: 6px; + background-color: #e5e5e5; + padding: 0 12px; + height: 28px; + line-height: 28px; + border: 1px solid #ccc; + box-sizing: border-box; +} + +.cron-input { + display: inline-block; + vertical-align: middle; + margin-top: 6px; + padding: 0 8px; + background-color: #fff; + border: 1px solid #ccc; + height: 28px; + line-height: 28px; + box-sizing: border-box; + width: 80px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; +} + +.cron-input:focus { + outline: 0; + border: 1px solid #01AAED; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 4px 0px #01AAED; + translate: 1s; +} + +.layui-cron .layui-form-checkbox[lay-skin="primary"] span { + padding-right: 10px; + min-width: 16px; +} + +.layui-cron .layui-form-checkbox[lay-skin="primary"] { + padding-left: 22px; + margin-top: 5px; +} +.layui-cron input[type=number] { + -moz-appearance:textfield; +} +.layui-cron input[type=number]::-webkit-inner-spin-button, +.layui-cron input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} +.cron-tips{ + color: grey; + line-height: 28px; + height: 28px; + display: inline-block; + vertical-align: middle; + margin-top: 8px; + margin-left: 5px; +} diff --git a/public/panel/modules/cron.js b/public/panel/modules/cron.js new file mode 100644 index 00000000..60e0c5a8 --- /dev/null +++ b/public/panel/modules/cron.js @@ -0,0 +1,970 @@ +/** + @ Name:layui.cron Cron表达式解析器 + @ Author:贝哥哥 + @ License:MIT + */ + +layui.define(['lay', 'element', 'form'], function (exports) { //假如该组件依赖 layui.form + var $ = layui.$, layer = layui.layer, lay = layui.lay, element = layui.element, form = layui.form + + + //字符常量 + , MOD_NAME = 'cron', ELEM = '.layui-cron', THIS = 'layui-this', SHOW = 'layui-show', HIDE = 'layui-hide' + + , ELEM_STATIC = 'layui-cron-static', ELEM_FOOTER = 'layui-cron-footer', ELEM_CONFIRM = '.cron-btns-confirm', + ELEM_HINT = 'layui-cron-hint' + + , ELEM_RUN_HINT = 'layui-cron-run-hint' + + //外部接口 + , cron = { + v: '2.0.0' // cron 组件当前版本 + , index: layui.cron ? (layui.cron.index + 10000) : 0 // corn 实例标识 + + //设置全局项 + , set: function (options) { + var that = this; + that.config = $.extend({}, that.config, options); + return that; + } + + //事件监听 + , on: function (events, callback) { + return layui.onevent.call(this, MOD_NAME, events, callback); + } + + //主体CSS等待事件 + , ready: function (fn) { + var cssPath = layui.cache.base + "cron.css?v=" + cron.v; + layui.link(cssPath, fn, "cron"); //此处的“cron”要对应 cron.css 中的样式: html #layuicss-cron{} + return this; + } + } + + //操作当前实例 + , thisIns = function () { + var that = this, options = that.config, id = options.id || options.index; + + return { + //提示框 + hint: function (content) { + that.hint.call(that, content); + }, config: options + } + } + + //构造器,创建实例 + , Class = function (options) { + var that = this; + that.index = ++cron.index; + that.config = $.extend({}, that.config, cron.config, options); + cron.ready(function () { + that.init(); + }); + }; + + //默认配置 + Class.prototype.config = { + value: null // 当前表达式值,每秒执行一次 + , + isInitValue: true //用于控制是否自动向元素填充初始值(需配合 value 参数使用) + , + lang: "cn" //语言,只支持cn/en,即中文和英文 + , + tabs: [{key: 'minutes', range: '0-59'}, { + key: 'hours', range: '0-23' + }, {key: 'days', range: '1-31'}, {key: 'months', range: '1-12'}, {key: 'weeks', range: '1-7'}], + defaultCron: {minutes: "*", hours: "*", days: "*", months: "*", weeks: "*"}, + trigger: "click" //呼出控件的事件 + , + btns: ['run', 'confirm'] //右下角显示的按钮,会按照数组顺序排列 + , + position: null //控件定位方式定位, 默认absolute,支持:fixed/absolute/static + , + zIndex: null //控件层叠顺序 + , + show: false //是否直接显示,如果设置 true,则默认直接显示控件 + , + showBottom: true //是否显示底部栏 + , + done: null //控件选择完毕后的回调,点击运行/确定也均会触发 + , + run: null // 最近运行时间接口 + }; + + //多语言 + Class.prototype.lang = function () { + var that = this, options = that.config, text = { + cn: { + tabs: [{title: "分"}, {title: "时"}, {title: "日"}, {title: "月"}, { + title: "周", + rateBegin: "第", + rateMid: "周的星期", + rateEnd: "" + }], + every: "每", + unspecified: "不指定", + period: "周期", + periodFrom: "从", + rate: "按照", + rateBegin: "从", + rateMid: "开始,每", + rateEnd: "执行一次", + weekday: "工作日", + weekdayPrefix: "每月", + weekdaySuffix: "号最近的那个工作日", + lastday: "本月最后一日", + lastweek: "本月最后一个星期", + custom: "指定", + tools: { + confirm: '确定', run: '运行' + }, + formatError: ['Cron格式不合法', '
已为你重置'] + }, en: { + tabs: [{title: "Minutes"}, {title: "Hours"}, {title: "Days"}, {title: "Months"}, {title: "Weeks"}], + every: "Every ", + unspecified: "Unspecified", + period: "Period", + periodFrom: "From", + rate: "According to", + rateBegin: "begin at", + rateMid: ", every", + rateEnd: " execute once", + weekday: "Weekday", + weekdayPrefix: "Every month at ", + weekdaySuffix: "号最近的那个工作日", + lastday: "Last day of the month", + lastweek: "本月最后一个星期", + custom: "Custom", + tools: { + confirm: 'Confirm', run: 'Run' + }, + formatError: ['The cron format error', '
It has been reset'] + } + }; + return text[options.lang] || text['cn']; + }; + + //初始准备 + Class.prototype.init = function () { + var that = this, options = that.config, isStatic = options.position === 'static'; + + options.elem = lay(options.elem); + + options.eventElem = lay(options.eventElem); + + if (!options.elem[0]) return; + + //如果不是input|textarea元素,则默认采用click事件 + if (!that.isInput(options.elem[0])) { + if (options.trigger === 'focus') { + options.trigger = 'click'; + } + } + + // 设置渲染所绑定元素的唯一KEY + if (!options.elem.attr('lay-key')) { + options.elem.attr('lay-key', that.index); + options.eventElem.attr('lay-key', that.index); + } + + // 当前实例主面板ID + that.elemID = 'layui-icon' + options.elem.attr('lay-key'); + + //默认赋值 + if (options.value && options.isInitValue) { + that.setValue(options.value); + } + if (!options.value) { + options.value = options.elem[0].value || ''; + } + var cronArr = options.value.split(' '); + if (cronArr.length >= 6) { + options.cron = { + minutes: cronArr[0], + hours: cronArr[1], + days: cronArr[2], + months: cronArr[3], + weeks: cronArr[4], + }; + } else { + options.cron = lay.extend({}, options.defaultCron); + } + + + if (options.show || isStatic) that.render(); + isStatic || that.events(); + + + }; + + + // 控件主体渲染 + Class.prototype.render = function () { + var that = this, options = that.config, lang = that.lang(), isStatic = options.position === 'static', + tabFilter = 'cron-tab' + options.elem.attr('lay-key') + //主面板 + , elem = that.elem = lay.elem('div', { + id: that.elemID, 'class': ['layui-cron', isStatic ? (' ' + ELEM_STATIC) : ''].join('') + }) + + // tab 内容区域 + , elemTab = that.elemTab = lay.elem('div', { + 'class': 'layui-tab layui-tab-card', 'lay-filter': tabFilter + }), tabHead = lay.elem('ul', { + 'class': 'layui-tab-title' + }), tabContent = lay.elem('div', { + 'class': 'layui-tab-content' + }) + + //底部区域 + , divFooter = that.footer = lay.elem('div', { + 'class': ELEM_FOOTER + }); + + if (options.zIndex) elem.style.zIndex = options.zIndex; + + // 生成tab 内容区域 + elemTab.appendChild(tabHead); + elemTab.appendChild(tabContent); + lay.each(lang.tabs, function (i, item) { + // 表头 + var li = lay.elem('li', { + 'class': i === 0 ? THIS : "", 'lay-id': i + }); + li.innerHTML = item.title; + tabHead.appendChild(li); + + // 表体 + tabContent.appendChild(that.getTabContentChildElem(i)); + }); + + // 主区域 + elemMain = that.elemMain = lay.elem('div', { + 'class': 'layui-cron-main' + }); + elemMain.appendChild(elemTab); + + //生成底部栏 + lay(divFooter).html(function () { + var html = [], btns = []; + lay.each(options.btns, function (i, item) { + var title = lang.tools[item] || 'btn'; + btns.push('' + title + ''); + }); + html.push(''); + return html.join(''); + }()); + + //插入到主区域 + elem.appendChild(elemMain); + + options.showBottom && elem.appendChild(divFooter); + + + //移除上一个控件 + that.remove(Class.thisElemCron); + + //如果是静态定位,则插入到指定的容器中,否则,插入到body + isStatic ? options.elem.append(elem) : (document.body.appendChild(elem) + , that.position()); + + + that.checkCron(); + + that.elemEvent(); // 主面板事件 + + Class.thisElemCron = that.elemID; + + form.render(); + + } + + // 渲染 tab 子控件 + Class.prototype.getTabContentChildElem = function (index) { + var that = this, options = that.config, tabItem = options.tabs[index], tabItemKey = tabItem.key, + lang = that.lang(), tabItemLang = lang.tabs[index], cron = options.cron, + formFilter = 'cronForm' + tabItemKey + options.elem.attr('lay-key'), data = function () { + if (cron[tabItemKey].indexOf('-') != -1) { + // 周期数据 + var arr = cron[tabItemKey].split('-'); + return { + type: 'range', start: arr[0], end: arr[1] + }; + } + if (cron[tabItemKey].indexOf('/') != -1) { + // 频率数据 + var arr = cron[tabItemKey].split('/'); + return { + type: 'rate', begin: arr[0], rate: arr[1] + }; + } + if (cron[tabItemKey].indexOf(',') != -1 || /^\+?[0-9][0-9]*$/.test(cron[tabItemKey])) { + // 按照指定执行 + var arr = cron[tabItemKey].split(',').map(Number); + return { + type: 'custom', values: arr + }; + } + if (cron[tabItemKey].indexOf('W') != -1) { + // 最近的工作日 + var value = cron[tabItemKey].replace('W', ''); + return { + type: 'weekday', value: value + }; + } + if (index === 2 && cron[tabItemKey] === 'L') { + // 本月最后一日 + return { + type: 'lastday', value: 'L' + }; + } + if (index === 4 && cron[tabItemKey].indexOf('L') != -1) { + // 本月最后一个周 value + var value = cron[tabItemKey].replace('L', ''); + return { + type: 'lastweek', value: value + }; + } + if (cron[tabItemKey] === '*') { + // 每次 + return { + type: 'every', value: '*' + }; + } + if (cron[tabItemKey] === '?' || cron[tabItemKey] === undefined || cron[tabItemKey] === '') { + // 不指定 + return { + //type: 'unspecified', value: cron[tabItemKey] + type: 'every', value: '*' + }; + } + }(), rangeData = function () { + if (tabItem.range) { + var arr = tabItem.range.split('-'); + return { + min: parseInt(arr[0]), max: parseInt(arr[1]) + }; + } + }(); + var elem = lay.elem('div', { + 'class': 'layui-tab-item layui-form ' + (index === 0 ? SHOW : ""), 'lay-filter': formFilter + }); + + // 每次 + elem.appendChild(function () { + var everyRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', + 'type': 'radio', + 'value': 'every', + 'title': lang.every + tabItemLang.title + }); + if (data.type === 'every') { + lay(everyRadio).attr('checked', true); + } + var everyDiv = lay.elem('div', { + 'class': 'cron-row' + }); + everyDiv.appendChild(everyRadio); + return everyDiv; + }()); + + // 不指定,从日开始 + /*if (index >= 2) { + elem.appendChild(function () { + var unspecifiedRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'unspecified', 'title': lang.unspecified + }); + if (data.type === 'unspecified') { + lay(unspecifiedRadio).attr('checked', true); + } + var unspecifiedDiv = lay.elem('div', { + 'class': 'cron-row' + }); + unspecifiedDiv.appendChild(unspecifiedRadio); + return unspecifiedDiv; + }()); + }*/ + + // 周期 + var rangeChild = [function () { + var rangeRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'range', 'title': lang.period + }); + if (data.type === 'range') { + lay(rangeRadio).attr('checked', true); + } + return rangeRadio; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = lang.periodFrom; + return elem; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'rangeStart', 'value': data.start || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = '-'; + return elem; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'rangeEnd', 'value': data.end || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = tabItemLang.title; + return elem; + }()] + + , rangeDiv = lay.elem('div', { + 'class': 'cron-row' + }); + lay.each(rangeChild, function (i, item) { + rangeDiv.appendChild(item); + }); + if (tabItem.range) { + var rangeTip = lay.elem('div', { + 'class': 'cron-tips' + }); + rangeTip.innerHTML = ['(', tabItem.range, ')'].join(''); + rangeDiv.appendChild(rangeTip); + } + elem.appendChild(rangeDiv); + + // 频率,年没有 + if (index < 6) { + var rateChild = [function () { + var rateRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'rate', 'title': lang.rate + }); + if (data.type === 'rate') { + lay(rateRadio).attr('checked', true); + } + return rateRadio; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = tabItemLang.rateBegin || lang.rateBegin; + return elem; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'begin', 'value': data.begin || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = tabItemLang.rateMid || (tabItemLang.title + lang.rateMid); + return elem; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'rate', 'value': data.rate || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = undefined != tabItemLang.rateEnd ? tabItemLang.rateEnd : (tabItemLang.title + lang.rateEnd); + if (undefined != tabItemLang.rateEnd && tabItemLang.rateEnd === '') { + lay(elem).addClass(HIDE); + } + return elem; + }()] + + , rateDiv = lay.elem('div', { + 'class': 'cron-row' + }); + lay.each(rateChild, function (i, item) { + rateDiv.appendChild(item); + }); + if (tabItem.range) { + var rateTip = lay.elem('div', { + 'class': 'cron-tips' + }); + if (index === 4) { + // 周 + rateTip.innerHTML = '(1-4/1-7)'; + } else { + rateTip.innerHTML = ['(', rangeData.min, '/', (rangeData.max + (index <= 1 ? 1 : 0)), ')'].join(''); + } + rateDiv.appendChild(rateTip); + } + elem.appendChild(rateDiv); + } + + // 特殊:日(最近的工作日、最后一日),周(最后一周) + /*if (index === 2) { + // 日 + // 最近的工作日 + var weekChild = [function () { + var weekRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'weekday', 'title': lang.weekday + }); + if (data.type === 'weekday') { + lay(weekRadio).attr('checked', true); + } + return weekRadio; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = lang.weekdayPrefix; + return elem; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'weekday', 'value': data.value || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-input-mid' + }); + elem.innerHTML = lang.weekdaySuffix; + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-tips' + }); + elem.innerHTML = ['(', tabItem.range, ')'].join(''); + return elem; + }()] + + , weekDiv = lay.elem('div', { + 'class': 'cron-row' + }); + lay.each(weekChild, function (i, item) { + weekDiv.appendChild(item); + }); + elem.appendChild(weekDiv); + + // 本月最后一日 + elem.appendChild(function () { + var lastRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'lastday', 'title': lang.lastday + }); + if (data.type === 'lastday') { + lay(lastRadio).attr('checked', true); + } + var lastDiv = lay.elem('div', { + 'class': 'cron-row' + }); + lastDiv.appendChild(lastRadio); + return lastDiv; + }()); + + } + + if (index === 4) { + // 本月最后一个周几 + var lastWeekChild = [function () { + var lastWeekRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'lastweek', 'title': lang.lastweek + }); + if (data.type === 'lastweek') { + lay(lastWeekRadio).attr('checked', true); + } + return lastWeekRadio; + }(), function () { + var elem = lay.elem('input', { + 'class': 'cron-input', 'type': 'number', 'name': 'lastweek', 'value': data.value || '' + }); + return elem; + }(), function () { + var elem = lay.elem('div', { + 'class': 'cron-tips' + }); + elem.innerHTML = ['(', tabItem.range, ')'].join(''); + return elem; + }()] + + , lastWeekDiv = lay.elem('div', { + 'class': 'cron-row' + }); + lay.each(lastWeekChild, function (i, item) { + lastWeekDiv.appendChild(item); + }); + elem.appendChild(lastWeekDiv); + + }*/ + + // 指定 + if (index <= 4) { + elem.appendChild(function () { + var customRadio = lay.elem('input', { + 'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'custom', 'title': lang.custom + }); + if (data.type === 'custom') { + lay(customRadio).attr('checked', true); + } + var customDiv = lay.elem('div', { + 'class': 'cron-row' + }); + customDiv.appendChild(customRadio); + return customDiv; + }()); + + // 指定数值,时分秒显示两位数,自动补零 + elem.appendChild(function () { + var customGrid = lay.elem('div', { + 'class': 'cron-grid' + }); + var i = rangeData.min; + while (i <= rangeData.max) { + // 时分秒显示两位数,自动补零 + var gridItemValue = index <= 1 ? lay.digit(i, 2) : i; + var gridItem = lay.elem('input', { + 'type': 'checkbox', + 'title': gridItemValue, + 'lay-skin': 'primary', + 'name': tabItemKey + '[custom]', + 'value': i + }); + if (data.values && data.values.includes(i)) { + lay(gridItem).attr('checked', true); + } + customGrid.appendChild(gridItem); + i++; + } + return customGrid; + }()); + } + + + return elem; + } + + //是否输入框 + Class.prototype.isInput = function (elem) { + return /input|textarea/.test(elem.tagName.toLocaleLowerCase()); + }; + + // 绑定的元素事件处理 + Class.prototype.events = function () { + var that = this, options = that.config + + //绑定呼出控件事件 + , showEvent = function (elem, bind) { + elem.on(options.trigger, function () { + bind && (that.bindElem = this); + that.render(); + }); + }; + + if (!options.elem[0] || options.elem[0].eventHandler) return; + + showEvent(options.elem, 'bind'); + showEvent(options.eventElem); + + //绑定关闭控件事件 + lay(document).on('click', function (e) { + if (e.target === options.elem[0] || e.target === options.eventElem[0] || e.target === lay(options.closeStop)[0]) { + return; + } + that.remove(); + }).on('keydown', function (e) { + if (e.keyCode === 13) { + if (lay('#' + that.elemID)[0] && that.elemID === Class.thisElemDate) { + e.preventDefault(); + lay(that.footer).find(ELEM_CONFIRM)[0].click(); + } + } + }); + + //自适应定位 + lay(window).on('resize', function () { + if (!that.elem || !lay(ELEM)[0]) { + return false; + } + that.position(); + }); + + options.elem[0].eventHandler = true; + }; + + // 主面板事件 + Class.prototype.elemEvent = function () { + var that = this, options = that.config, tabFilter = 'cron-tab' + options.elem.attr('lay-key'); + + // 阻止主面板点击冒泡,避免因触发文档事件而关闭主面 + lay(that.elem).on('click', function (e) { + lay.stope(e); + }); + + // tab选项卡切换 + var lis = lay(that.elemTab).find('li'); + lis.on('click', function () { + var layid = lay(this).attr('lay-id'); + if (undefined === layid) { + return; + } + element.tabChange(tabFilter, layid); + }); + + // cron选项点击 + form.on('radio', function (data) { + var $parent = data.othis.parent(); + var formFilter = $parent.parent().attr('lay-filter'); + var formData = form.val(formFilter); + var radioType = data.value; + if ('range' === radioType) { + // 范围 + form.val(formFilter, { + rangeStart: formData.rangeStart || 0, rangeEnd: formData.rangeEnd || 2 + }); + } + if ('rate' === radioType) { + // 频率 + form.val(formFilter, { + begin: formData.begin || 0, rate: formData.rate || 2 + }); + } + if ('custom' === radioType) { + // custom + var $grid = $parent.next(); + if ($grid.find(':checkbox:checked').length <= 0) { + $grid.children(':checkbox:first').next().click() + } + } + if ('weekday' === radioType) { + // weekday + form.val(formFilter, { + weekday: formData.weekday || 1 + }); + } + if ('lastweek' === radioType) { + // lastweek + form.val(formFilter, { + lastweek: formData.lastweek || 1 + }); + } + + }); + + //gird checkbox点击时,自动选中radio + form.on('checkbox', function (data) { + //触发选项的点击事件 + var $parent = data.othis.parent().parent(); + //循环父级,找到子级的子级找到value="custom"的radio + var $radio = $parent.children().find('input[value="custom"]'); + //触发radio的点击事件 + $radio.next().click(); + }); + + //点击底部按钮 + lay(that.footer).find('span').on('click', function () { + var type = lay(this).attr('lay-type'); + that.tool(this, type); + }); + }; + + //底部按钮点击事件 + Class.prototype.tool = function (btn, type) { + var that = this, options = that.config, lang = that.lang(), isStatic = options.position === 'static', active = { + //运行 + run: function () { + var value = that.parse(); + var loading = layer.load(); + $.get(options.run, {cron: value}, function (res) { + layer.close(loading); + if (res.code !== 0) { + return that.hint(res.msg); + } + that.runHint(res.data); + }, 'json').fail(function () { + layer.close(loading); + that.hint('服务器异常!'); + }); + } + + //确定 + , confirm: function () { + var value = that.parse(); + that.done([value]); + that.setValue(value).remove() + } + }; + active[type] && active[type](); + }; + + //执行 done/change 回调 + Class.prototype.done = function (param, type) { + var that = this, options = that.config; + + param = param || [that.parse()]; + typeof options[type || 'done'] === 'function' && options[type || 'done'].apply(options, param); + + return that; + }; + + // 解析cron表达式 + Class.prototype.parse = function () { + var that = this, options = that.config, valueArr = []; + + lay.each(options.tabs, function (index, item) { + var key = item.key; + var formFilter = 'cronForm' + key + options.elem.attr('lay-key'); + var formData = form.val(formFilter); + var radioType = (key + '[type]'); + var current = ""; + if (formData[radioType] === 'every') { + // 每次 + current = "*"; + } + if (formData[radioType] === 'range') { + // 范围 + current = formData.rangeStart + "-" + formData.rangeEnd; + } + if (formData[radioType] === 'rate') { + // 频率 + current = formData.begin + "/" + formData.rate; + } + if (formData[radioType] === 'custom') { + // 指定 + var checkboxName = (item.key + '[custom]'); + var customArr = []; + $('input[name="' + checkboxName + '"]:checked').each(function () { + customArr.push($(this).val()); + }); + current = customArr.join(','); + } + if (formData[radioType] === 'weekday') { + // 每月 formData.weekday 号最近的那个工作日 + current = formData.weekday + "W"; + } + if (formData[radioType] === 'lastday') { + // 本月最后一日 + current = "L"; + } + if (formData[radioType] === 'lastweek') { + // 本月最后星期 + current = formData.lastweek + "L"; + } + + if (formData[radioType] === 'unspecified' && index != 6) { + // 不指定 + current = "?"; + } + if (current !== "") { + valueArr.push(current); + options.cron[key] = current; + } + }); + return valueArr.join(' '); + }; + + //控件移除 + Class.prototype.remove = function (prev) { + var that = this, options = that.config, elem = lay('#' + (prev || that.elemID)); + if (!elem[0]) return that; + + if (!elem.hasClass(ELEM_STATIC)) { + that.checkCron(function () { + elem.remove(); + }); + } + return that; + }; + + //定位算法 + Class.prototype.position = function () { + var that = this, options = that.config; + lay.position(that.bindElem || options.elem[0], that.elem, { + position: options.position + }); + return that; + }; + + //提示 + Class.prototype.hint = function (content) { + var that = this, options = that.config, div = lay.elem('div', { + 'class': ELEM_HINT + }); + + if (!that.elem) return; + + div.innerHTML = content || ''; + lay(that.elem).find('.' + ELEM_HINT).remove(); + that.elem.appendChild(div); + + clearTimeout(that.hinTimer); + that.hinTimer = setTimeout(function () { + lay(that.elem).find('.' + ELEM_HINT).remove(); + }, 3000); + }; + + //运行提示 + Class.prototype.runHint = function (runList) { + var that = this, options = that.config, div = lay.elem('div', { + 'class': ELEM_RUN_HINT + }); + // debugger; + if (!that.elem || !runList || !runList.length) return; + + + lay(div).html(function () { + var html = []; + lay.each(runList, function (i, item) { + html.push('
' + item + '
'); + }); + return html.join(''); + }()); + + lay(that.elem).find('.' + ELEM_RUN_HINT).remove(); + that.elem.appendChild(div); + }; + + //赋值 + Class.prototype.setValue = function (value = '') { + var that = this, options = that.config, elem = that.bindElem || options.elem[0], + valType = that.isInput(elem) ? 'val' : 'html' + + options.position === 'static' || lay(elem)[valType](value || ''); + elem.textContent = '生成'; + return this; + }; + + //cron校验 + Class.prototype.checkCron = function (fn) { + var that = this, options = that.config, lang = that.lang(), elem = that.bindElem || options.elem[0], + value = that.isInput(elem) ? elem.value : (options.position === 'static' ? '' : elem.innerHTML) + + , checkValid = function (value = "") { + + }; + + // cron 值,多个空格替换为一个空格,去掉首尾空格 + value = value || options.value; + if (typeof value === 'string') { + value = value.replace(/\s+/g, ' ').replace(/^\s|\s$/g, ''); + } + + if (fn === 'init') return checkValid(value), that; + + value = that.parse(); + if (value) { + that.setValue(value); + } + fn && fn(); + return that; + }; + + //核心入口 + cron.render = function (options) { + var ins = new Class(options); + return thisIns.call(ins); + }; + + exports('cron', cron); +}); diff --git a/public/panel/views/cron.html b/public/panel/views/cron.html new file mode 100644 index 00000000..d043a727 --- /dev/null +++ b/public/panel/views/cron.html @@ -0,0 +1,285 @@ + +计划任务 + +
+
+
添加计划任务
+
+
+
+ +
+ +
+
请填写任务名称
+
+
+ +
+ +
+
请务必正确填写执行周期
+
+
+ +
+
# 在此输入你要执行的脚本内容
+
+
+
+
+ + +
+
+
+
+
+
+
计划任务列表
+
+
+ + + + +
+
+
+ + diff --git a/public/panel/views/index.html b/public/panel/views/index.html index 83d57e57..4a31edf3 100644 --- a/public/panel/views/index.html +++ b/public/panel/views/index.html @@ -209,7 +209,7 @@ Date: 2023-06-22 let cpu_info admin.req({ url: '/api/panel/info/nowMonitor' - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { layer.msg('系统资源获取失败,请刷新重试!') @@ -276,7 +276,7 @@ Date: 2023-06-22 }) admin.req({ url: '/api/panel/info/systemInfo' - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { console.log('耗子Linux面板:系统信息获取失败,接口返回' + result) @@ -313,7 +313,7 @@ Date: 2023-06-22 admin.req( { url: '/api/panel/info/checkUpdate' - , method: 'get' + , type: 'get' , success: function (result) { layer.close(index) if (result.code !== 0) { @@ -331,35 +331,19 @@ Date: 2023-06-22 btn: ['是', '否'] }, function () { proxy = true - index = layer.msg('正在更新...', { icon: 16, time: 0, shade: 0.3 }) + index = layer.msg('正在更新,稍后请手动刷新...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/panel/info/update' - , method: 'post' + , type: '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 }) + index = layer.msg('正在更新,稍后请手动刷新...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/panel/info/update' - , method: 'post' + , type: 'post' , data: { proxy: proxy } - , success: function (result) { - layer.close(index) - if (result.code !== 0) { - return false - } - layer.alert('更新成功!') - location.href = '/'; - } }) }) diff --git a/public/panel/views/login.html b/public/panel/views/login.html index f016d1cb..27e1ab36 100644 --- a/public/panel/views/login.html +++ b/public/panel/views/login.html @@ -66,7 +66,7 @@ admin.req({ url: '/api/panel/user/login' , data: obj.field - , method: 'post' + , type: 'post' , done: function (res) { // 请求成功后,写入 access_token layui.data(setter.tableName, { diff --git a/public/panel/views/plugin.html b/public/panel/views/plugin.html index 8201b0a5..72c9d7bc 100644 --- a/public/panel/views/plugin.html +++ b/public/panel/views/plugin.html @@ -35,150 +35,152 @@ diff --git a/public/panel/views/plugins/openresty.html b/public/panel/views/plugins/openresty.html index 4a13191a..b449be64 100644 --- a/public/panel/views/plugins/openresty.html +++ b/public/panel/views/plugins/openresty.html @@ -68,7 +68,7 @@ Date: 2023-06-24 admin.req({ url: '/api/plugins/openresty/status' - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { console.log('耗子Linux面板:OpenResty运行状态获取失败,接口返回' + result) @@ -86,7 +86,7 @@ Date: 2023-06-24 // 获取openresty错误日志并渲染 admin.req({ url: '/api/plugins/openresty/errorLog' - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { console.log('耗子Linux面板:OpenResty错误日志获取失败,接口返回' + result) @@ -114,7 +114,7 @@ Date: 2023-06-24 // 获取openresty配置并渲染 admin.req({ url: '/api/plugins/openresty/config' - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { console.log('耗子Linux面板:OpenResty主配置获取失败,接口返回' + result) @@ -144,7 +144,7 @@ Date: 2023-06-24 index = layer.msg('正在启动OpenResty...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/start' - , method: 'post' + , type: 'post' , success: function (result) { layer.close(index) if (result.code !== 0) { @@ -162,7 +162,7 @@ Date: 2023-06-24 index = layer.msg('正在停止OpenResty...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/stop' - , method: 'post' + , type: 'post' , success: function (result) { layer.close(index) if (result.code !== 0) { @@ -181,7 +181,7 @@ Date: 2023-06-24 index = layer.msg('正在重启OpenResty...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/restart' - , method: 'post' + , type: 'post' , success: function (result) { layer.close(index) if (result.code !== 0) { @@ -197,7 +197,7 @@ Date: 2023-06-24 index = layer.msg('正在重载OpenResty...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/reload' - , method: 'post' + , type: 'post' , success: function (result) { layer.close(index) if (result.code !== 0) { @@ -212,7 +212,7 @@ Date: 2023-06-24 index = layer.msg('正在保存OpenResty主配置...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/config' - , method: 'post' + , type: 'post' , data: { config: openresty_config_editor.getValue() } @@ -229,7 +229,7 @@ Date: 2023-06-24 index = layer.msg('正在清空OpenResty错误日志...', { icon: 16, time: 0, shade: 0.3 }) admin.req({ url: '/api/plugins/openresty/clearErrorLog' - , method: 'post' + , type: 'post' , success: function (result) { layer.close(index) if (result.code !== 0) { diff --git a/public/panel/views/setting.html b/public/panel/views/setting.html new file mode 100644 index 00000000..3bdca849 --- /dev/null +++ b/public/panel/views/setting.html @@ -0,0 +1,180 @@ + +面板设置 +
+
+
+
+
设置
+
+ +
+ + +
+ +
+ +
+
修改面板的显示名称
+
+
+ +
+ +
+
修改面板的登录用户名
+
+
+ +
+ +
+
修改面板的登录密码(留空不修改)
+
+
+ +
+ +
+
修改面板账号的邮箱,目前用于签发免费SSL证书
+
+
+ +
+ +
+
修改面板的访问端口(保存后需要重启面板并修改浏览器地址栏的端口为新端口以访问面板) +
+
+
+ +
+ +
+
修改面板的访问入口(保存后需要重启面板并修改浏览器地址栏的入口为新入口以访问面板) +
+
+
+ +
+ +
+
修改面板的默认建站目录
+
+
+ +
+ +
+
修改面板的默认备份目录
+
+
+
+ +
+
+
+ +
+
+
+
+
+ + diff --git a/public/panel/views/task.html b/public/panel/views/task.html index f41d4918..5f48d352 100644 --- a/public/panel/views/task.html +++ b/public/panel/views/task.html @@ -56,7 +56,7 @@ Date: 2023-07-21 , $ = layui.jquery; admin.req({ url: "/api/panel/task/log?id=" + d.data.items[0].id - , method: 'get' + , type: 'get' , success: function (result) { if (result.code !== 0) { $('#plugin-install-log').html('实时安装日志获取失败,请刷新重试!'); @@ -90,7 +90,9 @@ Date: 2023-07-21 , {field: 'name', width: '100%', title: '任务名'} ]] , page: true - , text: '耗子Linux面板:数据加载出现异常!' + , text: { + none: '无数据' + } , parseData: function (res) { return { "code": res.code, @@ -117,7 +119,9 @@ Date: 2023-07-21 } ]] , page: true - , text: '耗子Linux面板:数据加载出现异常!' + , text: { + none: '无数据' + } , parseData: function (res) { return { "code": res.code, diff --git a/public/panel/views/website/add.html b/public/panel/views/website/add.html index 032cba24..fbf04a66 100644 --- a/public/panel/views/website/add.html +++ b/public/panel/views/website/add.html @@ -73,7 +73,7 @@ Date: 2023-06-24
@@ -139,7 +139,7 @@ Date: 2023-06-24 } admin.req({ url: '/api/panel/website/add' - , method: 'post' + , type: 'post' , data: data.field , success: function (result) { if (result.code !== 0) { diff --git a/public/panel/views/website/list.html b/public/panel/views/website/list.html index 66879851..dd53f808 100644 --- a/public/panel/views/website/list.html +++ b/public/panel/views/website/list.html @@ -54,7 +54,7 @@ Date: 2023-06-24 // 获取已安装的PHP和DB版本 admin.req({ url: '/api/panel/info/installedDbAndPhp' - , method: 'get' + , type: 'get' , success: function (result) { php = result.data.php mysql = result.data.mysql @@ -124,7 +124,7 @@ Date: 2023-06-24 layer.confirm('删除网站将一并删除站点目录(不包括数据库),是否继续?', function (index) { admin.req({ url: '/api/panel/website/delete' - , method: 'post' + , type: 'post' , data: data , success: function (result) { if (result.code !== 0) { @@ -143,7 +143,7 @@ Date: 2023-06-24 admin.req({ url: '/api/panel/website/getSiteSettings?name=' + data.name - , method: 'get' + , type: 'get' , beforeSend: function (request) { layer.load() } @@ -197,7 +197,7 @@ Date: 2023-06-24 , data = obj.data // 得到行数据 admin.req({ url: '/api/panel/website/updateSiteNote' - , method: 'post' + , type: 'post' , data: { name: data.name, note: value @@ -221,7 +221,7 @@ Date: 2023-06-24 admin.req({ url: '/api/panel/website/setSiteStatus' - , method: 'post' + , type: 'post' , data: { name: website_name, status: status diff --git a/routes/web.go b/routes/web.go index db7246a9..41b84b85 100644 --- a/routes/web.go +++ b/routes/web.go @@ -49,6 +49,7 @@ func Web() { r.Prefix("cron").Middleware(middleware.Jwt()).Group(func(r route.Route) { cronController := controllers.NewCronController() r.Get("list", cronController.List) + r.Get("script", cronController.Script) r.Post("add", cronController.Add) r.Post("update", cronController.Update) r.Post("delete", cronController.Delete) diff --git a/scripts/install_panel.sh b/scripts/install_panel.sh index ef0486c0..76d15297 100644 --- a/scripts/install_panel.sh +++ b/scripts/install_panel.sh @@ -120,14 +120,14 @@ Prepare_system() { dnf config-manager --set-enabled crb /usr/bin/crb enable dnf makecache - dnf install -y curl wget zip unzip tar git jq git-core + dnf install -y curl wget zip unzip tar git jq git-core dox2unix elif [ "${OS}" == "debian" ]; then if [[ ${ipLocation} =~ "中国" ]]; then sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list fi apt update - apt install -y curl wget zip unzip tar git jq git + apt install -y curl wget zip unzip tar git jq git dox2unix else echo -e $HR echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。" @@ -156,10 +156,10 @@ Auto_Swap() { } Init_Panel() { + mkdir ${setup_Path}/server mkdir ${setup_Path}/server/cron mkdir ${setup_Path}/server/cron/logs - chmod -R 644 ${setup_Path}/server/cron - chmod -R 644 ${setup_Path}/server/cron/logs + chmod -R 644 ${setup_Path}/server mkdir ${setup_Path}/panel rm -rf ${setup_Path}/panel/* # 下载面板zip包并解压