mirror of
https://github.com/acepanel/panel.git
synced 2026-02-07 02:07:26 +08:00
feat: update
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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';\"")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const (
|
||||
SettingKeyMonitorDays = "monitor_days"
|
||||
SettingKeyBackupPath = "backup_path"
|
||||
SettingKeyWebsitePath = "website_path"
|
||||
SettingKeyPanelEntrance = "panel_entrance"
|
||||
SettingKeyEntrance = "entrance"
|
||||
SettingKeyMysqlRootPassword = "mysql_root_password"
|
||||
)
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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] != '/' {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
194
public/panel/modules/cron.css
Normal file
194
public/panel/modules/cron.css
Normal 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;
|
||||
}
|
||||
970
public/panel/modules/cron.js
Normal file
970
public/panel/modules/cron.js
Normal file
@@ -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格式不合法', '<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);
|
||||
});
|
||||
285
public/panel/views/cron.html
Normal file
285
public/panel/views/cron.html
Normal 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: '任务名 <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> 执行周期 <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>
|
||||
@@ -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 = '/';
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
180
public/panel/views/setting.html
Normal file
180
public/panel/views/setting.html
Normal 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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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包并解压
|
||||
|
||||
Reference in New Issue
Block a user