mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 13:47:15 +08:00
feat(cron): support backup
This commit is contained in:
@@ -2,11 +2,15 @@ package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
@@ -39,6 +43,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
action := ctx.Argument(0)
|
||||
arg1 := ctx.Argument(1)
|
||||
arg2 := ctx.Argument(2)
|
||||
arg3 := ctx.Argument(3)
|
||||
arg4 := ctx.Argument(4)
|
||||
|
||||
switch action {
|
||||
case "init":
|
||||
@@ -130,6 +136,15 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
case "getEntrance":
|
||||
color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyEntrance, "/"))
|
||||
|
||||
case "deleteEntrance":
|
||||
err := services.NewSettingImpl().Set(models.SettingKeyEntrance, "/")
|
||||
if err != nil {
|
||||
color.Redln("删除面板入口失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除面板入口成功")
|
||||
|
||||
case "writePlugin":
|
||||
slug := arg1
|
||||
version := arg2
|
||||
@@ -198,6 +213,176 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Greenln("清理任务成功")
|
||||
|
||||
case "backup":
|
||||
backupType := arg1
|
||||
name := arg2
|
||||
path := arg3
|
||||
save := arg4
|
||||
hr := `+----------------------------------------------------`
|
||||
if len(backupType) == 0 || len(name) == 0 || len(path) == 0 || len(save) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Greenln("★ 开始备份 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
|
||||
if !tools.Exists(path) {
|
||||
tools.Mkdir(path, 0644)
|
||||
}
|
||||
|
||||
switch backupType {
|
||||
case "website":
|
||||
color.Yellowln("|-目标网站: " + name)
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
|
||||
color.Redln("|-网站不存在")
|
||||
color.Greenln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
|
||||
tools.ExecShell(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`)
|
||||
color.Greenln("|-备份成功")
|
||||
|
||||
case "mysql":
|
||||
rootPassword := services.NewSettingImpl().Get(models.SettingKeyMysqlRootPassword)
|
||||
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
|
||||
|
||||
err := os.Setenv("MYSQL_PWD", rootPassword)
|
||||
if err != nil {
|
||||
color.Redln("|-备份MySQL数据库失败: " + err.Error())
|
||||
color.Greenln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("|-目标MySQL数据库: " + name)
|
||||
color.Greenln("|-开始导出")
|
||||
tools.ExecShell(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`)
|
||||
color.Greenln("|-导出成功")
|
||||
color.Greenln("|-开始压缩")
|
||||
tools.ExecShell("cd /tmp && zip -r " + backupFile + ".zip " + backupFile)
|
||||
tools.RemoveFile("/tmp/" + backupFile)
|
||||
color.Greenln("|-压缩成功")
|
||||
color.Greenln("|-开始移动")
|
||||
tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip")
|
||||
color.Greenln("|-移动成功")
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
color.Greenln("|-备份成功")
|
||||
|
||||
case "postgresql":
|
||||
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
|
||||
check := tools.ExecShell(`su - postgres -c "psql -l" 2>&1`)
|
||||
if strings.Contains(check, name) {
|
||||
color.Redln("|-数据库不存在")
|
||||
color.Greenln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("|-目标PostgreSQL数据库: " + name)
|
||||
color.Greenln("|-开始导出")
|
||||
tools.ExecShell(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`)
|
||||
color.Greenln("|-导出成功")
|
||||
color.Greenln("|-开始压缩")
|
||||
tools.ExecShell("cd /tmp && zip -r " + backupFile + ".zip " + backupFile)
|
||||
tools.RemoveFile("/tmp/" + backupFile)
|
||||
color.Greenln("|-压缩成功")
|
||||
color.Greenln("|-开始移动")
|
||||
tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip")
|
||||
color.Greenln("|-移动成功")
|
||||
color.Greenln("|-备份成功")
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
return nil
|
||||
}
|
||||
var filteredFiles []os.FileInfo
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), name) && strings.HasSuffix(file.Name(), ".zip") {
|
||||
fileInfo, err := os.Stat(filepath.Join(path, file.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
filteredFiles = append(filteredFiles, fileInfo)
|
||||
}
|
||||
}
|
||||
sort.Slice(filteredFiles, func(i, j int) bool {
|
||||
return filteredFiles[i].ModTime().After(filteredFiles[j].ModTime())
|
||||
})
|
||||
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
|
||||
fileToDelete := filepath.Join(path, filteredFiles[i].Name())
|
||||
color.Yellowln("|-清理备份: " + fileToDelete)
|
||||
tools.RemoveFile(fileToDelete)
|
||||
}
|
||||
color.Greenln("|-清理完成")
|
||||
color.Greenln(hr)
|
||||
color.Greenln("☆ 备份完成 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
|
||||
case "cutoff":
|
||||
name := arg1
|
||||
save := arg2
|
||||
hr := `+----------------------------------------------------`
|
||||
if len(name) == 0 || len(save) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Greenln("★ 开始切割 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
|
||||
color.Yellowln("|-目标网站: " + name)
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
|
||||
color.Redln("|-网站不存在")
|
||||
color.Greenln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
logPath := "/www/wwwlogs/" + website.Name + ".log"
|
||||
if !tools.Exists(logPath) {
|
||||
color.Redln("|-日志文件不存在")
|
||||
color.Greenln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
backupPath := "/www/wwwlogs/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".log.zip"
|
||||
tools.ExecShell(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log")
|
||||
tools.ExecShell(`echo "" > ` + logPath)
|
||||
color.Greenln("|-切割成功")
|
||||
|
||||
color.Greenln(hr)
|
||||
files, err := os.ReadDir("/www/wwwlogs")
|
||||
if err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
return nil
|
||||
}
|
||||
var filteredFiles []os.FileInfo
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), website.Name) && strings.HasSuffix(file.Name(), ".log.zip") {
|
||||
fileInfo, err := os.Stat(filepath.Join("/www/wwwlogs", file.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
filteredFiles = append(filteredFiles, fileInfo)
|
||||
}
|
||||
}
|
||||
sort.Slice(filteredFiles, func(i, j int) bool {
|
||||
return filteredFiles[i].ModTime().After(filteredFiles[j].ModTime())
|
||||
})
|
||||
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
|
||||
fileToDelete := filepath.Join("/www/wwwlogs", filteredFiles[i].Name())
|
||||
color.Yellowln("|-清理日志: " + fileToDelete)
|
||||
tools.RemoveFile(fileToDelete)
|
||||
}
|
||||
color.Greenln("|-清理完成")
|
||||
color.Greenln(hr)
|
||||
color.Greenln("☆ 切割完成 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
|
||||
case "writeSite":
|
||||
name := arg1
|
||||
@@ -290,12 +475,14 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
default:
|
||||
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
|
||||
color.Greenln("请使用以下命令:")
|
||||
color.Greenln("panel update {proxy} 更新/修复面板到最新版本")
|
||||
color.Greenln("panel update {proxy} 更新 / 修复面板到最新版本")
|
||||
color.Greenln("panel getInfo 重新初始化面板账号信息")
|
||||
color.Greenln("panel getPort 获取面板访问端口")
|
||||
color.Greenln("panel getEntrance 获取面板访问入口")
|
||||
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} 备份网站/MySQL数据库/PostgreSQL数据库到指定目录")
|
||||
color.Greenln("panel deleteEntrance 删除面板访问入口")
|
||||
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
|
||||
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
|
||||
color.Redln("以下命令请在开发者指导下使用:")
|
||||
color.Yellowln("panel init 初始化面板")
|
||||
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
|
||||
@@ -303,7 +490,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
|
||||
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
|
||||
color.Yellowln("panel deleteSite {name} 删除面板网站数据")
|
||||
color.Yellowln("panel writeSetting {name} {value} 写入/更新面板设置数据")
|
||||
color.Yellowln("panel writeSetting {name} {value} 写入 / 更新面板设置数据")
|
||||
color.Yellowln("panel deleteSetting {name} 删除面板设置数据")
|
||||
}
|
||||
|
||||
|
||||
@@ -7,23 +7,25 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type CronController struct {
|
||||
cron services.Cron
|
||||
cron services.Cron
|
||||
setting services.Setting
|
||||
}
|
||||
|
||||
func NewCronController() *CronController {
|
||||
return &CronController{
|
||||
cron: services.NewCronImpl(),
|
||||
cron: services.NewCronImpl(),
|
||||
setting: services.NewSettingImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CronController) List(ctx http.Context) {
|
||||
func (c *CronController) List(ctx http.Context) {
|
||||
limit := ctx.Request().QueryInt("limit")
|
||||
page := ctx.Request().QueryInt("page")
|
||||
|
||||
@@ -42,11 +44,13 @@ func (r *CronController) List(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *CronController) Add(ctx http.Context) {
|
||||
func (c *CronController) Add(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
"time": "required",
|
||||
"script": "required",
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
"time": "required",
|
||||
"script": "required",
|
||||
"type": "required|in:shell,backup,cutoff",
|
||||
"backup_type": "required_if:type,backup|in:website,mysql,postgresql",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
@@ -63,7 +67,46 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 写入shell
|
||||
shell := ctx.Request().Input("script")
|
||||
cronType := ctx.Request().Input("type")
|
||||
if cronType == "backup" {
|
||||
backupType := ctx.Request().Input("backup_type")
|
||||
backupName := ctx.Request().Input("backup_database")
|
||||
if backupType == "website" {
|
||||
backupName = ctx.Request().Input("website")
|
||||
}
|
||||
backupPath := ctx.Request().Input("backup_path", c.setting.Get(models.SettingKeyBackupPath)+"/"+backupType)
|
||||
backupSave := ctx.Request().InputInt("save", 10)
|
||||
shell = `#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
# 耗子面板 - 数据备份脚本
|
||||
|
||||
type=` + backupType + `
|
||||
path=` + backupPath + `
|
||||
name=` + backupName + `
|
||||
save=` + cast.ToString(backupSave) + `
|
||||
|
||||
# 执行备份
|
||||
panel backup ${type} ${name} ${path} ${save} 2>&1
|
||||
`
|
||||
}
|
||||
if cronType == "cutoff" {
|
||||
website := ctx.Request().Input("website")
|
||||
save := ctx.Request().InputInt("save", 180)
|
||||
shell = `#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
# 耗子面板 - 日志切割脚本
|
||||
|
||||
name=` + website + `
|
||||
save=` + cast.ToString(save) + `
|
||||
|
||||
# 执行切割
|
||||
panel cutoff ${name} ${save} 2>&1
|
||||
`
|
||||
}
|
||||
|
||||
shellDir := "/www/server/cron/"
|
||||
shellLogDir := "/www/server/cron/logs/"
|
||||
if !tools.Exists(shellDir) {
|
||||
@@ -77,7 +120,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"), 0700) {
|
||||
if !tools.WriteFile(shellDir+shellFile+".sh", shell, 0700) {
|
||||
facades.Log().Error("[面板][CronController] 创建计划任务脚本失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
@@ -86,7 +129,7 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
|
||||
var cron models.Cron
|
||||
cron.Name = ctx.Request().Input("name")
|
||||
cron.Type = "shell"
|
||||
cron.Type = ctx.Request().Input("type")
|
||||
cron.Status = true
|
||||
cron.Time = ctx.Request().Input("time")
|
||||
cron.Shell = shellDir + shellFile + ".sh"
|
||||
@@ -99,7 +142,7 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.cron.AddToSystem(cron)
|
||||
c.cron.AddToSystem(cron)
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"id": cron.ID,
|
||||
@@ -107,7 +150,7 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Script 获取脚本内容
|
||||
func (r *CronController) Script(ctx http.Context) {
|
||||
func (c *CronController) Script(ctx http.Context) {
|
||||
var cron models.Cron
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
@@ -118,7 +161,7 @@ func (r *CronController) Script(ctx http.Context) {
|
||||
Success(ctx, tools.ReadFile(cron.Shell))
|
||||
}
|
||||
|
||||
func (r *CronController) Update(ctx http.Context) {
|
||||
func (c *CronController) Update(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
"time": "required",
|
||||
@@ -167,15 +210,15 @@ func (r *CronController) Update(ctx http.Context) {
|
||||
}
|
||||
tools.ExecShell("dos2unix " + cron.Shell)
|
||||
|
||||
r.cron.DeleteFromSystem(cron)
|
||||
c.cron.DeleteFromSystem(cron)
|
||||
if cron.Status {
|
||||
r.cron.AddToSystem(cron)
|
||||
c.cron.AddToSystem(cron)
|
||||
}
|
||||
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *CronController) Delete(ctx http.Context) {
|
||||
func (c *CronController) Delete(ctx http.Context) {
|
||||
var cron models.Cron
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
@@ -183,7 +226,7 @@ func (r *CronController) Delete(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.cron.DeleteFromSystem(cron)
|
||||
c.cron.DeleteFromSystem(cron)
|
||||
tools.RemoveFile(cron.Shell)
|
||||
|
||||
_, err = facades.Orm().Query().Delete(&cron)
|
||||
@@ -196,7 +239,7 @@ func (r *CronController) Delete(ctx http.Context) {
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *CronController) Status(ctx http.Context) {
|
||||
func (c *CronController) Status(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"status": "bool",
|
||||
})
|
||||
@@ -224,15 +267,15 @@ func (r *CronController) Status(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.cron.DeleteFromSystem(cron)
|
||||
c.cron.DeleteFromSystem(cron)
|
||||
if cron.Status {
|
||||
r.cron.AddToSystem(cron)
|
||||
c.cron.AddToSystem(cron)
|
||||
}
|
||||
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *CronController) Log(ctx http.Context) {
|
||||
func (c *CronController) Log(ctx http.Context) {
|
||||
var cron models.Cron
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,8 +23,10 @@ func NewCronImpl() *CronImpl {
|
||||
func (r *CronImpl) AddToSystem(cron models.Cron) {
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/root")
|
||||
tools.ExecShell("systemctl restart crond")
|
||||
} else {
|
||||
tools.ExecShell("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/crontabs/root")
|
||||
tools.ExecShell("systemctl restart cron")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +36,9 @@ func (r *CronImpl) DeleteFromSystem(cron models.Cron) {
|
||||
cron.Shell = strings.ReplaceAll(cron.Shell, "/", "\\/")
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root")
|
||||
tools.ExecShell("systemctl restart crond")
|
||||
} else {
|
||||
tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root")
|
||||
tools.ExecShell("systemctl restart cron")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,3 +124,29 @@ func Empty(path string) bool {
|
||||
|
||||
return len(files) == 0
|
||||
}
|
||||
|
||||
// Mv 移动路径
|
||||
func Mv(src, dst string) bool {
|
||||
cmd := exec.Command("mv", src, dst)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 移动 %s 到 %s 失败: %s", src, dst, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Cp 复制路径
|
||||
func Cp(src, dst string) bool {
|
||||
cmd := exec.Command("cp", "-r", src, dst)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 复制 %s 到 %s 失败: %s", src, dst, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// `=---=' //
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
|
||||
// 佛祖保佑 永无Bug 永不宕机 //
|
||||
// Name: 耗子Linux面板 Author: 耗子 Date: 2023-06-22 //
|
||||
// Name: 耗子Linux面板 Author: 耗子 Date: 2023-07-25 //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
-->
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdnjs.cdn.haozi.net/layui/2.8.8/css/layui.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cdn.haozi.net/layui/2.8.11/css/layui.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="Panel_app"></div>
|
||||
<script src="https://cdnjs.cdn.haozi.net/layui/2.8.8/layui.min.js"></script>
|
||||
<script src="https://cdnjs.cdn.haozi.net/layui/2.8.11/layui.min.js"></script>
|
||||
<script src="https://cdnjs.cdn.haozi.net/ace/1.6.1/ace.js"></script>
|
||||
<script src="https://cdnjs.cdn.haozi.net/echarts/5.4.2/echarts.min.js"></script>
|
||||
<script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,22 @@ Date: 2023-07-21
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">添加计划任务</div>
|
||||
<div class="layui-card-body">
|
||||
<blockquote class="layui-elem-quote">
|
||||
<p>面板的计划任务均基于脚本运行,若任务类型满足不了需求,可自行修改对应的脚本。</p>
|
||||
</blockquote>
|
||||
<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">
|
||||
<select name="type" lay-verify="required" lay-filter="cron-type">
|
||||
<option value="shell">运行脚本</option>
|
||||
<option value="backup">备份数据</option>
|
||||
<option value="cutoff">切割日志</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请选择任务类型</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-name-input">
|
||||
<label class="layui-form-label">任务名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="name" lay-verify="required" placeholder="请输入任务名称"
|
||||
@@ -18,6 +32,61 @@ Date: 2023-07-21
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请填写任务名称</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-backup-type-input" style="display: none;">
|
||||
<label class="layui-form-label">备份类型</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" lay-filter="cron-add-backup-type-radio" name="backup_type" value="website"
|
||||
title="网站目录" checked="">
|
||||
<input type="radio" lay-filter="cron-add-backup-type-radio" name="backup_type" value="mysql"
|
||||
title="MySQL数据库">
|
||||
<input type="radio" lay-filter="cron-add-backup-type-radio" name="backup_type"
|
||||
value="postgresql"
|
||||
title="PostgreSQL数据库">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-website-input" style="display: none;">
|
||||
<label class="layui-form-label">网站</label>
|
||||
<div class="layui-input-inline">
|
||||
<script type="text/html" template lay-url="/api/panel/website/list?page=1&limit=10000">
|
||||
<select name="website" lay-filter="cron-add-website">
|
||||
{{# layui.each(d.data.items, function(index, item){ }}
|
||||
{{# if(index == 0){ }}
|
||||
<option value="{{ item.name }}" selected="">{{ item.name }}</option>
|
||||
{{# }else{ }}
|
||||
<option value="{{ item.name }}">{{ item.name }}</option>
|
||||
{{# } }}
|
||||
{{# }); }}
|
||||
</select>
|
||||
</script>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请选择网站</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-backup-database-input" style="display: none;">
|
||||
<label class="layui-form-label">数据库名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="backup_database" placeholder="请输入数据库名"
|
||||
autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请填写数据库名</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-backup-path-input" style="display: none;">
|
||||
<label class="layui-form-label">保存目录</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="backup_path" placeholder=""
|
||||
autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">
|
||||
目录可以为对象存储挂载目录,不填则默认为面板设置的备份目录
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="cron-add-save-input" style="display: none;">
|
||||
<label class="layui-form-label">保留份数</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="number" name="save" placeholder="10"
|
||||
autocomplete="off" class="layui-input" min="0" step="1" lay-affix="number">
|
||||
</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">
|
||||
@@ -26,11 +95,12 @@ Date: 2023-07-21
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请务必正确填写执行周期</div>
|
||||
</div>
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<div class="layui-form-item layui-form-text" id="cron-add-shell">
|
||||
<label class="layui-form-label">脚本内容</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="cron-add-script-editor"
|
||||
style="height: 250px;"># 在此输入你要执行的脚本内容</div>
|
||||
style="height: 250px;"># 在此输入你要执行的脚本内容
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
@@ -52,13 +122,22 @@ Date: 2023-07-21
|
||||
<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-type">
|
||||
{{# if(d.type == 'shell'){ }}
|
||||
运行脚本
|
||||
{{# }else if(d.type == 'backup'){ }}
|
||||
备份数据
|
||||
{{# }else if(d.type == 'cutoff'){ }}
|
||||
切割日志
|
||||
{{# } }}
|
||||
</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' : '' }}>
|
||||
{{ d.status ? 'checked' : '' }}>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,10 +173,11 @@ Date: 2023-07-21
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID'}
|
||||
, {field: 'name', width: 150, title: '任务名', sort: true}
|
||||
, {field: 'type', width: 150, title: '任务类型', sort: true}
|
||||
, {field: 'type', width: 150, title: '任务类型', templet: '#cron-table-type', sort: true}
|
||||
, {field: 'status', title: '启用', width: 100, templet: '#cron-table-status', unresize: true}
|
||||
, {field: 'time', width: 200, title: '任务周期(cron表达式)'}
|
||||
, {field: 'updated_at', title: '上次运行时间'}
|
||||
, {field: 'created_at', title: '创建时间'}
|
||||
, {field: 'updated_at', title: '最后更新时间'}
|
||||
, {
|
||||
field: 'edit',
|
||||
width: 180,
|
||||
@@ -120,8 +200,6 @@ Date: 2023-07-21
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 工具条
|
||||
table.on('tool(panel-cron)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'log') {
|
||||
@@ -233,6 +311,46 @@ Date: 2023-07-21
|
||||
}
|
||||
});
|
||||
|
||||
form.on('select(cron-type)', function (data) {
|
||||
if (data.value === 'shell') {
|
||||
$("#cron-add-backup-type-input").hide();
|
||||
$("#cron-add-save-input").hide();
|
||||
$('#cron-add-website-input').hide();
|
||||
$('#cron-add-backup-database-input').hide();
|
||||
$('#cron-add-backup-path-input').hide();
|
||||
$("#cron-add-shell").show();
|
||||
} else if (data.value === 'backup') {
|
||||
let selectedType = $('input[name="backup_type"]:checked').val();
|
||||
if (selectedType === 'website') {
|
||||
$('#cron-add-website-input').show();
|
||||
$('#cron-add-backup-database-input').hide();
|
||||
} else {
|
||||
$('#cron-add-website-input').hide();
|
||||
$('#cron-add-backup-database-input').show();
|
||||
}
|
||||
$("#cron-add-backup-type-input").show();
|
||||
$('#cron-add-backup-path-input').show();
|
||||
$("#cron-add-save-input").show();
|
||||
$("#cron-add-shell").hide();
|
||||
} else if (data.value === 'cutoff') {
|
||||
$('#cron-add-website-input').show();
|
||||
$('#cron-add-save-input').show();
|
||||
$("#cron-add-shell").hide();
|
||||
$('#cron-add-backup-database-input').hide();
|
||||
$("#cron-add-backup-type-input").hide();
|
||||
$('#cron-add-backup-path-input').hide();
|
||||
}
|
||||
});
|
||||
form.on('radio(cron-add-backup-type-radio)', function (data) {
|
||||
if (data.value == 'website') {
|
||||
$('#cron-add-website-input').show();
|
||||
$('#cron-add-backup-database-input').hide();
|
||||
} else {
|
||||
$('#cron-add-website-input').hide();
|
||||
$('#cron-add-backup-database-input').show();
|
||||
}
|
||||
});
|
||||
|
||||
form.on('switch(cron-status)', function (obj) {
|
||||
let $ = layui.$;
|
||||
let id = $(this).data('id');
|
||||
@@ -263,7 +381,6 @@ Date: 2023-07-21
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:计划任务添加失败,接口返回' + result);
|
||||
layer.msg('计划任务添加失败!')
|
||||
return false;
|
||||
}
|
||||
@@ -274,7 +391,6 @@ Date: 2023-07-21
|
||||
, btn: ['确定']
|
||||
, yes: function (index) {
|
||||
layer.closeAll();
|
||||
//location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user