2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 02:07:26 +08:00

feat: update

This commit is contained in:
耗子
2023-07-21 04:32:41 +08:00
parent 0d4f8bbea1
commit 0ab4738e1d
22 changed files with 1933 additions and 286 deletions

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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';\"")

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -8,7 +8,7 @@ const (
SettingKeyMonitorDays = "monitor_days"
SettingKeyBackupPath = "backup_path"
SettingKeyWebsitePath = "website_path"
SettingKeyPanelEntrance = "panel_entrance"
SettingKeyEntrance = "entrance"
SettingKeyMysqlRootPassword = "mysql_root_password"
)

View File

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

View File

@@ -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] != '/' {

View File

@@ -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)
}

View File

@@ -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;
}

View File

@@ -0,0 +1,970 @@
/**
@ Namelayui.cron Cron表达式解析器
@ Author贝哥哥
@ LicenseMIT
*/
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格式不合法', '<br>已为你重置']
}, 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', '<br>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('<span lay-type="' + item + '" class="cron-btns-' + item + '">' + title + '</span>');
});
html.push('<div class="cron-footer-btns">' + btns.join('') + '</div>');
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('<div class="cron-run-list">' + item + '</div>');
});
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);
});

View File

@@ -0,0 +1,285 @@
<!--
Name: 计划任务
Author: 耗子
Date: 2023-07-21
-->
<title>计划任务</title>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">添加计划任务</div>
<div class="layui-card-body">
<form class="layui-form" action="" lay-filter="cron-add-form">
<div class="layui-form-item">
<label class="layui-form-label">任务名</label>
<div class="layui-input-inline">
<input type="text" name="name" lay-verify="required" placeholder="请输入任务名称"
autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">请填写任务名称</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">执行周期</label>
<div class="layui-input-inline">
<input type="text" name="time" id="cron-add-time"
lay-verify="required" placeholder="请选择或输入cron表达式" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">请务必正确填写执行周期</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">脚本内容</label>
<div class="layui-input-block">
<div id="cron-add-script-editor"
style="height: 250px;"># 在此输入你要执行的脚本内容</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit="" lay-filter="cron-add-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">计划任务列表</div>
<div class="layui-card-body">
<table id="panel-cron" lay-filter="panel-cron"></table>
<!-- 操作按钮模板 -->
<script type="text/html" id="cron-table-edit">
<a class="layui-btn layui-btn-xs" lay-event="log">日志</a>
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 运行开关 -->
<script type="text/html" id="cron-table-status">
<input type="checkbox" name="cron-status" lay-skin="switch" lay-text="ON|OFF"
lay-filter="cron-status"
value="{{ d.status }}" data-id="{{ d.id }}"
{{ d.status==
1 ? 'checked' : '' }}>
</script>
</div>
</div>
</div>
<script>
var cronAddScriptEditor = ace.edit("cron-add-script-editor", {
mode: "ace/mode/sh",
selectionStyle: "text"
});
var cronEditScriptEditor;
layui.use(['admin', 'table', 'jquery', 'cron'], function () {
var $ = layui.$
, form = layui.form
, table = layui.table
, admin = layui.admin
, cron = layui.cron;
cron.render({
elem: "#cron-add-time",
btns: ['confirm'],
show: false,
done: function (value) {
$('#cron-add-time').val(value);
}
});
form.render();
table.render({
elem: '#panel-cron'
, url: '/api/panel/cron/list'
, cols: [[
{field: 'id', hide: true, title: 'ID'}
, {field: 'name', width: 150, title: '任务名', sort: true}
, {field: 'type', width: 150, title: '任务类型', sort: true}
, {field: 'status', title: '启用', width: 100, templet: '#cron-table-status', unresize: true}
, {field: 'time', width: 200, title: '任务周期cron表达式'}
, {field: 'updated_at', title: '上次运行时间'}
, {
field: 'edit',
width: 180,
title: '操作',
templet: '#cron-table-edit',
fixed: 'right',
align: 'left'
}
]]
, page: true
, text: {
none: '暂无数据'
}
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
});
// 工具条
table.on('tool(panel-cron)', function (obj) {
let data = obj.data;
if (obj.event === 'log') {
// 打开日志弹窗
admin.popup({
title: '日志'
,
area: ['80%', '80%']
,
id: 'cron-log'
,
content: '<pre id="cron-log-view" style="overflow: auto; height: 95%;border: 0 none; line-height:23px; padding: 15px; margin: 0; white-space: pre-wrap; background-color: rgb(51,51,51); color:#f1f1f1; border-radius:0;"></pre>'
,
success: function (layero, index) {
admin.req({
url: '/api/panel/cron/log?id=' + data.id
, type: 'GET'
, success: function (res) {
if (res.code === 0) {
$('#cron-log-view').html(res.data);
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
}
});
} else if (obj.event === 'edit') {
// 打开编辑弹窗
admin.req({
url: '/api/panel/cron/script?id=' + data.id
, type: 'GET'
, success: function (res) {
if (res.code === 0) {
admin.popup({
title: '编辑'
,
area: ['80%', '80%']
,
id: 'cron-log'
,
content: '任务名&nbsp;&nbsp;&nbsp;&nbsp;<div class="layui-input-inline" style="width: 190px;"><input type="text" name="cron-edit-name" placeholder="请输入任务名称" autocomplete="off" class="layui-input" value="' + data.name + '"></div>&nbsp;&nbsp;&nbsp;&nbsp;执行周期&nbsp;&nbsp;&nbsp;&nbsp;<div class="layui-input-inline" style="width: 190px;"><input id="cron-edit-time-' + data.id + '" type="text" name="cron-edit-time" placeholder="请输入执行周期" autocomplete="off" class="layui-input" value="' + data.time + '"/></div><hr><div id="cron-edit-script-editor" style="height: 80%;">' + res.data + '</div><br><button id="cron-edit-' + data.id + '" class="layui-btn">保存</button>'
,
success: function (layero, index) {
cronEditScriptEditor = ace.edit("cron-edit-script-editor", {
mode: "ace/mode/sh",
selectionStyle: "text"
});
cron.render({
elem: "#cron-edit-time-" + data.id,
btns: ['confirm'],
show: false,
done: function (value) {
$('#cron-add-time').val(value);
}
});
$('#cron-edit-' + data.id).click(function () {
admin.req({
url: '/api/panel/cron/update'
, type: 'POST'
, data: {
id: data.id,
name: $('input[name="cron-edit-name"]').val(),
time: $('input[name="cron-edit-time"]').val(),
script: cronEditScriptEditor.getValue()
}
, success: function (res) {
if (res.code === 0) {
layer.msg('保存成功', {icon: 1, time: 1000});
table.reload('panel-cron');
layer.close(index);
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
}
});
} else {
layer.msg(res.message, {icon: 2, time: 1000});
}
}
});
} else if (obj.event === 'del') {
layer.confirm('确定删除计划任务' + data.name + '吗?', function (index) {
layer.close(index);
admin.req({
url: '/api/panel/cron/delete',
type: 'POST',
data: {
id: data.id
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-cron');
layer.msg('计划任务:' + data.name + ' 已删除', {
icon: 1,
time: 1000
});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
}
});
form.on('switch(cron-status)', function (obj) {
let $ = layui.$;
let id = $(this).data('id');
let status = obj.elem.checked;
admin.req({
url: '/api/panel/cron/status',
type: 'POST',
data: {
id: id,
status: status
}
, success: function (res) {
if (res.code === 0) {
layer.msg('设置成功', {icon: 1, time: 1000});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
form.on('submit(cron-add-submit)', function (data) {
data.field.script = cronAddScriptEditor.getValue();
admin.req({
url: "/api/panel/cron/add"
, type: 'post'
, data: data.field
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板计划任务添加失败接口返回' + result);
layer.msg('计划任务添加失败!')
return false;
}
table.reload('panel-cron');
layer.alert('计划任务添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
//location.reload();
}
});
}
});
return false;
});
});
</script>

View File

@@ -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 = '/';
}
})
})

View File

@@ -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, {

View File

@@ -35,150 +35,152 @@
</div>
<script>
layui.use(['admin', 'table', 'jquery'], function () {
var $ = layui.$
, form = layui.form
, table = layui.table
, admin = layui.admin
layui.use(['admin', 'table', 'jquery'], function () {
var $ = layui.$
, form = layui.form
, table = layui.table
, admin = layui.admin
table.render({
elem: '#panel-plugin'
, url: '/api/panel/plugin/list'
, cols: [[
{ field: 'slug', hide: true, title: 'Slug' }
, { field: 'name', width: 150, title: '插件名', sort: true }
, { field: 'description', title: '描述' }
, { field: 'author', width: 100, title: '作者' }
, { field: 'installed_version', width: 140, title: '已装版本' }
, { field: 'version', width: 140, title: '最新版本' }
, { field: 'show', title: '首页显示', width: 90, templet: '#plugin-show', unresize: true }
, {
field: 'control',
width: 180,
title: '操作',
templet: '#panel-plugin-control',
fixed: 'right',
align: 'left'
}
]]
, page: false
, text: '耗子Linux面板数据加载出现异常'
, done: function () {
//element.render('progress');
}
})
// 工具条
table.on('tool(panel-plugin)', function (obj) {
let data = obj.data
if (obj.event === 'open') {
location.hash = '/plugins/' + data.slug
} else if (obj.event === 'install') {
layer.confirm('确定安装该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/install',
type: 'POST',
data: {
slug: data.slug
table.render({
elem: '#panel-plugin'
, url: '/api/panel/plugin/list'
, cols: [[
{field: 'slug', hide: true, title: 'Slug'}
, {field: 'name', width: 150, title: '插件名', sort: true}
, {field: 'description', title: '描述'}
, {field: 'author', width: 100, title: '作者'}
, {field: 'installed_version', width: 140, title: '已装版本'}
, {field: 'version', width: 140, title: '最新版本'}
, {field: 'show', title: '首页显示', width: 90, templet: '#plugin-show', unresize: true}
, {
field: 'control',
width: 180,
title: '操作',
templet: '#panel-plugin-control',
fixed: 'right',
align: 'left'
}
]]
, page: false
, text: {
none: '无数据'
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
})
} else {
layer.msg(res.msg, { icon: 2, time: 1000 })
}
, done: function () {
//element.render('progress');
}
})
})
} else if (obj.event === 'uninstall') {
layer.confirm('确定卸载该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/uninstall',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
// 工具条
table.on('tool(panel-plugin)', function (obj) {
let data = obj.data
if (obj.event === 'open') {
location.hash = '/plugins/' + data.slug
} else if (obj.event === 'install') {
layer.confirm('确定安装该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/install',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
})
} else {
layer.msg(res.msg, {icon: 2, time: 1000})
}
}
})
})
} else {
layer.msg(res.msg, { icon: 2, time: 1000 })
}
}
})
})
} else if (obj.event === 'update') {
layer.confirm('确定升级该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/update',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('升级:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
} else if (obj.event === 'uninstall') {
layer.confirm('确定卸载该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/uninstall',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
})
} else {
layer.msg(res.msg, {icon: 2, time: 1000})
}
}
})
})
} else if (obj.event === 'update') {
layer.confirm('确定升级该插件吗?', function (index) {
layer.close(index)
admin.req({
url: '/api/panel/plugin/update',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
table.reload('panel-plugin')
layer.msg('升级:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
})
} else {
layer.msg(res.msg, {icon: 2, time: 1000})
}
}
})
})
} else {
layer.msg(res.msg, { icon: 2, time: 1000 })
}
}
})
})
}
form.on('switch(plugin-show-home)', function (obj) {
let $ = layui.$
let plugin_slug = $(this).data('plugin-slug')
let show = obj.elem.checked ? 1 : 0
admin.req({
url: '/api/panel/plugin/updateShow',
type: 'POST',
data: {
slug: plugin_slug,
show: show
}
, success: function (res) {
if (res.code === 0) {
layer.msg('设置成功', {icon: 1, time: 1000})
} else {
// 还原开关状态
obj.elem.checked = !obj.elem.checked
form.render('checkbox')
layer.msg(res.msg, {icon: 2, time: 1000})
}
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error)
}
})
})
/*form.render(null, 'plugin-form');
//搜索
form.on('submit(plugin-search-submit)', function (data) {
var field = data.field;
//执行重载
table.reload('plugin-search-submit', {
where: field
});
});*/
})
form.on('switch(plugin-show-home)', function (obj) {
let $ = layui.$
let plugin_slug = $(this).data('plugin-slug')
let show = obj.elem.checked ? 1 : 0
admin.req({
url: '/api/panel/plugin/updateShow',
type: 'POST',
data: {
slug: plugin_slug,
show: show
}
, success: function (res) {
if (res.code === 0) {
layer.msg('设置成功', { icon: 1, time: 1000 })
} else {
// 还原开关状态
obj.elem.checked = !obj.elem.checked
form.render('checkbox')
layer.msg(res.msg, { icon: 2, time: 1000 })
}
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error)
}
})
})
/*form.render(null, 'plugin-form');
//搜索
form.on('submit(plugin-search-submit)', function (data) {
var field = data.field;
//执行重载
table.reload('plugin-search-submit', {
where: field
});
});*/
})
</script>

View File

@@ -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) {

View File

@@ -0,0 +1,180 @@
<!--
Name: 面板设置
Author: 耗子
Date: 2023-07-21
-->
<title>面板设置</title>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">设置</div>
<div class="layui-card-body">
<div class="layui-form" lay-filter="panel_setting">
<!--<div class="layui-form-item">
<label class="layui-form-label">API 开关</label>
<div class="layui-input-inline">
<input type="checkbox" name="api" lay-skin="switch" lay-text="ON|OFF">
</div>
<div class="layui-form-mid layui-word-aux">开启后将提供面板API接口的访问支持</div>
</div>
<div id="setting-api-token" class="layui-form-item">
<label class="layui-form-label">API Token</label>
<div class="layui-input-inline">
<input type="text" name="api_token" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">API Token用于携带访问面板接口</div>
</div>-->
<!--<div class="layui-form-item">
<label class="layui-form-label">多设备登录</label>
<div class="layui-input-inline">
<input type="checkbox" name="multi_login" lay-skin="switch" lay-text="ON|OFF">
</div>
<div class="layui-form-mid layui-word-aux">
开启后将允许多设备同时登录面板,可能具有一定安全隐患
</div>
</div>-->
<div class="layui-form-item">
<label class="layui-form-label">面板名称</label>
<div class="layui-input-inline">
<input type="text" name="name" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的显示名称</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面板用户名</label>
<div class="layui-input-inline">
<input type="text" name="username" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的登录用户名</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面板密码</label>
<div class="layui-input-inline">
<input type="password" name="password" value="" class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的登录密码(留空不修改)</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面板邮箱</label>
<div class="layui-input-inline">
<input type="text" name="email" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板账号的邮箱目前用于签发免费SSL证书</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面板端口</label>
<div class="layui-input-inline">
<input type="text" name="port" value="" class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的访问端口(<b style="color: red;">保存后需要重启面板并修改浏览器地址栏的端口为新端口以访问面板</b>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">面板入口</label>
<div class="layui-input-inline">
<input type="text" name="entrance" value="" class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的访问入口(<b style="color: red;">保存后需要重启面板并修改浏览器地址栏的入口为新入口以访问面板</b>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">建站目录</label>
<div class="layui-input-inline">
<input type="text" name="website_path" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的默认建站目录</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备份目录</label>
<div class="layui-input-inline">
<input type="text" name="backup_path" value="获取中ing..." class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">修改面板的默认备份目录</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="panel_setting_submit">确认修改</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.define(['form', 'upload'], function () {
var $ = layui.$
, layer = layui.layer
, admin = layui.admin
, form = layui.form;
// 渲染表单
form.render();
$('#setting-api-token').hide();
// ajax获取设置项并赋值
admin.req({
url: "/api/panel/setting/list"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板系统信息获取失败接口返回' + result);
layer.msg('系统信息获取失败,请刷新重试!')
return false;
}
console.log(result)
form.val("panel_setting",
result.data
);
$('input').attr('disabled', false);
if (result.data.api === 1) {
$('#setting-api-token').show();
$('#setting-api-token input').attr('readonly', true);
}
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
// 面板设置
form.on('submit(panel_setting_submit)', function (obj) {
// 面板API
if (obj.field.api === "on") {
obj.field.api = 1;
} else {
obj.field.api = 0;
}
// 多设备登录
if (obj.field.multi_login === "on") {
obj.field.multi_login = 1;
} else {
obj.field.multi_login = 0;
}
// 提交修改
admin.req({
url: "/api/panel/setting/save"
, type: 'post'
, data: obj.field
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板设置保存失败接口返回' + result);
layer.msg('面板设置保存失败,请刷新重试!')
return false;
}
admin.render();
layer.msg('面板设置保存成功!');
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
return false;
});
});
</script>

View File

@@ -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,

View File

@@ -73,7 +73,7 @@ Date: 2023-06-24
<label class="layui-form-label">目录</label>
<div class="layui-input-block">
<input type="text" name="path"
placeholder="请输入网站根目录(不填默认为/www/wwwroot/网站名)"
placeholder="请输入网站根目录(不填默认为建站目录/网站名)"
autocomplete="off" class="layui-input">
</div>
</div>
@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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包并解压