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('