2
0
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:
耗子
2023-07-25 03:21:00 +08:00
parent a128d295ea
commit ce7a380e6e
7 changed files with 1241 additions and 850 deletions

View File

@@ -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} 删除面板设置数据")
}

View File

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

View File

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

View File

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

View File

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

View File

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