1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-01 03:24:14 +08:00

feat: 计划任务脚本支持选择用户及解释器 (#6721)

Refs #3933 #2738
This commit is contained in:
ssongliu 2024-10-15 16:05:21 +08:00 committed by GitHub
parent f3fd02fcb3
commit 86bf7ea73a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 392 additions and 180 deletions

View File

@ -86,6 +86,21 @@ func (b *BaseApi) UpdateDeviceByFile(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Device
// @Summary Load user list
// @Description 获取服务器用户列表
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/users [get]
func (b *BaseApi) LoadUsers(c *gin.Context) {
users, err := deviceService.LoadUsers()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, users)
}
// @Tags Device // @Tags Device
// @Summary Update device // @Summary Update device
// @Description 修改系统参数 // @Description 修改系统参数

View File

@ -21,9 +21,13 @@ type CronjobCreate struct {
SpecCustom bool `json:"specCustom"` SpecCustom bool `json:"specCustom"`
Spec string `json:"spec" validate:"required"` Spec string `json:"spec" validate:"required"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"` Script string `json:"script"`
Command string `json:"command"` Command string `json:"command"`
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
User string `json:"user"`
AppID string `json:"appID"` AppID string `json:"appID"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
@ -44,9 +48,13 @@ type CronjobUpdate struct {
SpecCustom bool `json:"specCustom"` SpecCustom bool `json:"specCustom"`
Spec string `json:"spec" validate:"required"` Spec string `json:"spec" validate:"required"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"` Script string `json:"script"`
Command string `json:"command"` Command string `json:"command"`
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
User string `json:"user"`
AppID string `json:"appID"` AppID string `json:"appID"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
@ -89,9 +97,13 @@ type CronjobInfo struct {
SpecCustom bool `json:"specCustom"` SpecCustom bool `json:"specCustom"`
Spec string `json:"spec"` Spec string `json:"spec"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"` Script string `json:"script"`
Command string `json:"command"` Command string `json:"command"`
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
User string `json:"user"`
AppID string `json:"appID"` AppID string `json:"appID"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`

View File

@ -14,9 +14,13 @@ type Cronjob struct {
SpecCustom bool `json:"specCustom"` SpecCustom bool `json:"specCustom"`
Spec string `gorm:"not null" json:"spec"` Spec string `gorm:"not null" json:"spec"`
Executor string `json:"executor"`
Command string `json:"command"` Command string `json:"command"`
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"` Script string `json:"script"`
User string `json:"user"`
Website string `json:"website"` Website string `json:"website"`
AppID string `json:"appID"` AppID string `json:"appID"`
DBType string `json:"dbType"` DBType string `json:"dbType"`
@ -39,6 +43,7 @@ type JobRecords struct {
BaseModel BaseModel
CronjobID uint `json:"cronjobID"` CronjobID uint `json:"cronjobID"`
TaskID string `json:"taskID"`
StartTime time.Time `json:"startTime"` StartTime time.Time `json:"startTime"`
Interval float64 `json:"interval"` Interval float64 `json:"interval"`
Records string `json:"records"` Records string `json:"records"`

View File

@ -182,13 +182,13 @@ func (u *CronjobService) HandleOnce(id uint) error {
return nil return nil
} }
func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error { func (u *CronjobService) Create(req dto.CronjobCreate) error {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByName(cronjobDto.Name)) cronjob, _ := cronjobRepo.Get(commonRepo.WithByName(req.Name))
if cronjob.ID != 0 { if cronjob.ID != 0 {
return constant.ErrRecordExist return constant.ErrRecordExist
} }
cronjob.Secret = cronjobDto.Secret cronjob.Secret = req.Secret
if err := copier.Copy(&cronjob, &cronjobDto); err != nil { if err := copier.Copy(&cronjob, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error()) return errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
cronjob.Status = constant.StatusEnable cronjob.Status = constant.StatusEnable
@ -282,8 +282,11 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["spec_custom"] = req.SpecCustom upMap["spec_custom"] = req.SpecCustom
upMap["spec"] = spec upMap["spec"] = spec
upMap["script"] = req.Script upMap["script"] = req.Script
upMap["script_mode"] = req.ScriptMode
upMap["command"] = req.Command upMap["command"] = req.Command
upMap["container_name"] = req.ContainerName upMap["container_name"] = req.ContainerName
upMap["executor"] = req.Executor
upMap["user"] = req.User
upMap["app_id"] = req.AppID upMap["app_id"] = req.AppID
upMap["website"] = req.Website upMap["website"] = req.Website
upMap["exclusion_rules"] = req.ExclusionRules upMap["exclusion_rules"] = req.ExclusionRules

View File

@ -8,13 +8,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/ntp" "github.com/1Panel-dev/1Panel/agent/utils/ntp"
@ -35,15 +34,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
} }
record.Records = u.generateLogsPath(*cronjob, record.StartTime) record.Records = u.generateLogsPath(*cronjob, record.StartTime)
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
script := cronjob.Script err = u.handleShell(*cronjob, record.Records)
if len(cronjob.ContainerName) != 0 {
command := "sh"
if len(cronjob.Command) != 0 {
command = cronjob.Command
}
script = fmt.Sprintf("docker exec %s %s -c \"%s\"", cronjob.ContainerName, command, strings.ReplaceAll(cronjob.Script, "\"", "\\\""))
}
err = u.handleShell(cronjob.Type, cronjob.Name, script, record.Records)
u.removeExpiredLog(*cronjob) u.removeExpiredLog(*cronjob)
case "curl": case "curl":
if len(cronjob.URL) == 0 { if len(cronjob.URL) == 0 {
@ -51,7 +42,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
} }
record.Records = u.generateLogsPath(*cronjob, record.StartTime) record.Records = u.generateLogsPath(*cronjob, record.StartTime)
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL), record.Records) err = cmd.ExecShell(record.Records, 24*time.Hour, "bash", "-c", "curl", cronjob.URL)
u.removeExpiredLog(*cronjob) u.removeExpiredLog(*cronjob)
case "ntp": case "ntp":
err = u.handleNtpSync() err = u.handleNtpSync()
@ -100,17 +91,29 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}() }()
} }
func (u *CronjobService) handleShell(cronType, cornName, script, logPath string) error { func (u *CronjobService) handleShell(cronjob model.Cronjob, logPath string) error {
handleDir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronType, cornName) if len(cronjob.ContainerName) != 0 {
if _, err := os.Stat(handleDir); err != nil && os.IsNotExist(err) { command := "sh"
if err = os.MkdirAll(handleDir, os.ModePerm); err != nil { if len(cronjob.Command) != 0 {
command = cronjob.Command
}
scriptFile, _ := os.ReadFile(cronjob.Script)
return cmd.ExecShell(logPath, 24*time.Hour, "docker", "exec", cronjob.ContainerName, command, "-c", strings.ReplaceAll(string(scriptFile), "\"", "\\\""))
}
if cronjob.ScriptMode == "input" {
fileItem := path.Join(global.CONF.System.BaseDir, "1panel", "task", "shell", cronjob.Name, cronjob.Name+".sh")
_ = os.MkdirAll(path.Dir(fileItem), os.ModePerm)
shellFile, err := os.OpenFile(fileItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err return err
} }
} defer shellFile.Close()
if err := cmd.ExecCronjobWithTimeOut(script, handleDir, logPath, 24*time.Hour); err != nil { if _, err := shellFile.WriteString(cronjob.Script); err != nil {
return err return err
} }
return nil return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, fileItem)
}
return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script)
} }
func (u *CronjobService) handleNtpSync() error { func (u *CronjobService) handleNtpSync() error {

View File

@ -37,6 +37,7 @@ type IDeviceService interface {
LoadTimeZone() ([]string, error) LoadTimeZone() ([]string, error)
CheckDNS(key, value string) (bool, error) CheckDNS(key, value string) (bool, error)
LoadConf(name string) (string, error) LoadConf(name string) (string, error)
LoadUsers() ([]string, error)
Scan() dto.CleanData Scan() dto.CleanData
Clean(req []dto.Clean) Clean(req []dto.Clean)
@ -168,6 +169,21 @@ func (u *DeviceService) Update(key, value string) error {
return nil return nil
} }
func (u *DeviceService) LoadUsers() ([]string, error) {
file, err := os.ReadFile("/etc/passwd")
if err != nil {
return nil, err
}
var users []string
lines := strings.Split(string(file), "\n")
for _, line := range lines {
if strings.Contains(line, ":") {
users = append(users, strings.Split(line, ":")[0])
}
}
return users, nil
}
func (u *DeviceService) UpdateHosts(req []dto.HostHelper) error { func (u *DeviceService) UpdateHosts(req []dto.HostHelper) error {
conf, err := os.ReadFile(defaultHostPath) conf, err := os.ReadFile(defaultHostPath)
if err != nil { if err != nil {

View File

@ -58,6 +58,7 @@ const (
TaskBuild = "TaskBuild" TaskBuild = "TaskBuild"
TaskPull = "TaskPull" TaskPull = "TaskPull"
TaskPush = "TaskPush" TaskPush = "TaskPush"
TaskHandle = "TaskHandle"
) )
const ( const (
@ -65,6 +66,7 @@ const (
TaskScopeApp = "App" TaskScopeApp = "App"
TaskScopeRuntime = "Runtime" TaskScopeRuntime = "Runtime"
TaskScopeDatabase = "Database" TaskScopeDatabase = "Database"
TaskScopeCronjob = "Cronjob"
TaskScopeAppStore = "AppStore" TaskScopeAppStore = "AppStore"
TaskScopeSnapshot = "Snapshot" TaskScopeSnapshot = "Snapshot"
TaskScopeContainer = "Container" TaskScopeContainer = "Container"

View File

@ -227,6 +227,7 @@ TaskRollback: "Rollback"
TaskPull: "Pull" TaskPull: "Pull"
TaskBuild: "Build" TaskBuild: "Build"
TaskPush: "Push" TaskPush: "Push"
TaskHandle: "Execute"
Website: "Website" Website: "Website"
App: "App" App: "App"
Runtime: "Runtime" Runtime: "Runtime"

View File

@ -229,6 +229,7 @@ TaskRollback: "回滚"
TaskPull: "拉取" TaskPull: "拉取"
TaskBuild: "建構" TaskBuild: "建構"
TaskPush: "推送" TaskPush: "推送"
TaskHandle: "執行"
Website: "網站" Website: "網站"
App: "應用" App: "應用"
Runtime: "運行環境" Runtime: "運行環境"

View File

@ -231,6 +231,7 @@ TaskRollback: "回滚"
TaskPull: "拉取" TaskPull: "拉取"
TaskBuild: "构建" TaskBuild: "构建"
TaskPush: "推送" TaskPush: "推送"
TaskHandle: "执行"
Website: "网站" Website: "网站"
App: "应用" App: "应用"
Runtime: "运行环境" Runtime: "运行环境"

View File

@ -22,6 +22,7 @@ func Init() {
migrations.AddTaskDB, migrations.AddTaskDB,
migrations.UpdateAppInstall, migrations.UpdateAppInstall,
migrations.UpdateSnapshot, migrations.UpdateSnapshot,
migrations.UpdateCronjob,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -266,3 +266,10 @@ var UpdateSnapshot = &gormigrate.Migration{
return tx.AutoMigrate(&model.Snapshot{}) return tx.AutoMigrate(&model.Snapshot{})
}, },
} }
var UpdateCronjob = &gormigrate.Migration{
ID: "20241011-update-cronjob",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Cronjob{}, &model.JobRecords{})
},
}

View File

@ -12,6 +12,7 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi baseApi := v2.ApiGroupApp.BaseApi
{ {
toolboxRouter.POST("/device/base", baseApi.LoadDeviceBaseInfo) toolboxRouter.POST("/device/base", baseApi.LoadDeviceBaseInfo)
toolboxRouter.GET("/device/users", baseApi.LoadUsers)
toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption) toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption)
toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf) toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf)
toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost) toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost)

View File

@ -107,15 +107,14 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
return nil return nil
} }
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error { func ExecShell(outPath string, timeout time.Duration, name string, arg ...string) error {
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666) file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil { if err != nil {
return err return err
} }
defer file.Close() defer file.Close()
cmd := exec.Command("bash", "-c", cmdStr) cmd := exec.Command(name, arg...)
cmd.Dir = workdir
cmd.Stdout = file cmd.Stdout = file
cmd.Stderr = file cmd.Stderr = file
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {

View File

@ -10,11 +10,15 @@ export namespace Cronjob {
specs: Array<string>; specs: Array<string>;
specObjs: Array<SpecObj>; specObjs: Array<SpecObj>;
executor: string;
isExecutorCustom: boolean;
script: string; script: string;
scriptMode: string;
isCustom: boolean; isCustom: boolean;
command: string; command: string;
inContainer: boolean; inContainer: boolean;
containerName: string; containerName: string;
user: string;
appID: string; appID: string;
website: string; website: string;
exclusionRules: string; exclusionRules: string;

View File

@ -12,6 +12,9 @@ export const getDeviceBase = () => {
export const loadTimeZoneOptions = () => { export const loadTimeZoneOptions = () => {
return http.get<Array<string>>(`/toolbox/device/zone/options`); return http.get<Array<string>>(`/toolbox/device/zone/options`);
}; };
export const loadUsers = () => {
return http.get<Array<string>>(`/toolbox/device/users`);
};
export const updateDevice = (key: string, value: string) => { export const updateDevice = (key: string, value: string) => {
return http.post(`/toolbox/device/update/conf`, { key: key, value: value }, TimeoutEnum.T_60S); return http.post(`/toolbox/device/update/conf`, { key: key, value: value }, TimeoutEnum.T_60S);
}; };

View File

@ -13,6 +13,7 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { StreamLanguage } from '@codemirror/language'; import { StreamLanguage } from '@codemirror/language';
import { nginx } from './nginx'; import { nginx } from './nginx';
import { yaml } from '@codemirror/legacy-modes/mode/yaml'; import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'; import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { placeholder } from '@codemirror/view'; import { placeholder } from '@codemirror/view';
import { json } from '@codemirror/lang-json'; import { json } from '@codemirror/lang-json';
@ -93,6 +94,9 @@ const initCodeMirror = () => {
case 'json': case 'json':
extensions.push(json()); extensions.push(json());
break; break;
case 'shell':
extensions.push(StreamLanguage.define(shell));
break;
} }
let startState = EditorState.create({ let startState = EditorState.create({
doc: content.value, doc: content.value,

View File

@ -923,6 +923,7 @@ const message = {
saturday: 'Saturday', saturday: 'Saturday',
sunday: 'Sunday', sunday: 'Sunday',
shellContent: 'Script', shellContent: 'Script',
executor: 'Executor',
errRecord: 'Incorrect logging', errRecord: 'Incorrect logging',
errHandle: 'Cronjob execution failure', errHandle: 'Cronjob execution failure',
noRecord: 'The execution did not generate any logs', noRecord: 'The execution did not generate any logs',

View File

@ -878,6 +878,7 @@ const message = {
saturday: '周六', saturday: '周六',
sunday: '周日', sunday: '周日',
shellContent: '腳本內容', shellContent: '腳本內容',
executor: '解釋器',
errRecord: '錯誤的日誌記錄', errRecord: '錯誤的日誌記錄',
errHandle: '任務執行失敗', errHandle: '任務執行失敗',
noRecord: '當前計劃任務暫未產生記錄', noRecord: '當前計劃任務暫未產生記錄',

View File

@ -879,6 +879,7 @@ const message = {
saturday: '周六', saturday: '周六',
sunday: '周日', sunday: '周日',
shellContent: '脚本内容', shellContent: '脚本内容',
executor: '解释器',
errRecord: '错误的日志记录', errRecord: '错误的日志记录',
errHandle: '任务执行失败', errHandle: '任务执行失败',
noRecord: '当前计划任务暂未产生记录', noRecord: '当前计划任务暂未产生记录',

View File

@ -67,7 +67,7 @@
<el-form-item :label="$t('cronjob.taskName')" prop="name"> <el-form-item :label="$t('cronjob.taskName')" prop="name">
<el-input :disabled="dialogData.title === 'edit'" clearable v-model.trim="dialogData.rowData!.name" /> <el-input :disabled="dialogData.title === 'edit'" clearable v-model.trim="dialogData.rowData!.name" />
</el-form-item> </el-form-item>
<el-card>
<el-form-item :label="$t('cronjob.cronSpec')" prop="specCustom"> <el-form-item :label="$t('cronjob.cronSpec')" prop="specCustom">
<el-checkbox :label="$t('container.custom')" v-model="dialogData.rowData!.specCustom" /> <el-checkbox :label="$t('container.custom')" v-model="dialogData.rowData!.specCustom" />
</el-form-item> </el-form-item>
@ -118,7 +118,12 @@
<div class="append">{{ $t('commons.units.second') }}</div> <div class="append">{{ $t('commons.units.second') }}</div>
</template> </template>
</el-input> </el-input>
<el-popover placement="top-start" :title="$t('cronjob.nextTime')" width="200" trigger="click"> <el-popover
placement="top-start"
:title="$t('cronjob.nextTime')"
width="200"
trigger="click"
>
<div v-for="(time, index_t) of nextTimes" :key="index_t"> <div v-for="(time, index_t) of nextTimes" :key="index_t">
<el-tag class="mt-2">{{ time }}</el-tag> <el-tag class="mt-2">{{ time }}</el-tag>
</div> </div>
@ -147,9 +152,14 @@
<div v-if="dialogData.rowData!.specCustom"> <div v-if="dialogData.rowData!.specCustom">
<el-form-item prop="spec"> <el-form-item prop="spec">
<div v-for="(spec, index) of dialogData.rowData.specs" :key="index" style="width: 100%"> <div v-for="(spec, index) of dialogData.rowData.specs" :key="index">
<el-input style="width: 80%" v-model="dialogData.rowData.specs[index]" /> <el-input style="width: 80%" v-model="dialogData.rowData.specs[index]" />
<el-popover placement="top-start" :title="$t('cronjob.nextTime')" width="200" trigger="click"> <el-popover
placement="top-start"
:title="$t('cronjob.nextTime')"
width="200"
trigger="click"
>
<div v-for="(time, index_t) of nextTimes" :key="index_t"> <div v-for="(time, index_t) of nextTimes" :key="index_t">
<el-tag class="mt-2">{{ time }}</el-tag> <el-tag class="mt-2">{{ time }}</el-tag>
</div> </div>
@ -175,40 +185,132 @@
{{ $t('commons.button.add') }} {{ $t('commons.button.add') }}
</el-button> </el-button>
</div> </div>
</el-card>
<el-form-item v-if="hasScript()"> <div v-if="hasScript()">
<el-form-item class="mt-5">
<el-checkbox v-model="dialogData.rowData!.inContainer"> <el-checkbox v-model="dialogData.rowData!.inContainer">
{{ $t('cronjob.containerCheckBox') }} {{ $t('cronjob.containerCheckBox') }}
</el-checkbox> </el-checkbox>
</el-form-item> </el-form-item>
<el-form-item <el-card v-if="dialogData.rowData!.inContainer">
v-if="hasScript() && dialogData.rowData!.inContainer" <el-row :gutter="20">
:label="$t('cronjob.containerName')" <el-col :span="12">
prop="containerName" <el-form-item :label="$t('cronjob.containerName')" prop="containerName">
>
<el-select class="selectClass" v-model="dialogData.rowData!.containerName"> <el-select class="selectClass" v-model="dialogData.rowData!.containerName">
<el-option v-for="item in containerOptions" :key="item" :value="item" :label="item" /> <el-option
v-for="item in containerOptions"
:key="item"
:value="item"
:label="item"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item </el-col>
v-if="hasScript() && dialogData.rowData!.inContainer" <el-col :span="12">
:label="$t('container.command')" <el-form-item :label="$t('container.command')" prop="command" :rules="Rules.requiredInput">
prop="command" <el-checkbox border v-model="dialogData.rowData!.isCustom">
:rules="Rules.requiredInput"
>
<el-checkbox class="w-full" border v-model="dialogData.rowData!.isCustom">
{{ $t('container.custom') }} {{ $t('container.custom') }}
</el-checkbox> </el-checkbox>
<el-select style="width: calc(100% - 100px)" filterable clearable v-model="dialogData.rowData!.command"> <el-select
v-if="!dialogData.rowData!.isCustom"
style="width: calc(100% - 100px)"
filterable
clearable
v-model="dialogData.rowData!.command"
>
<el-option value="ash" label="/bin/ash" /> <el-option value="ash" label="/bin/ash" />
<el-option value="bash" label="/bin/bash" /> <el-option value="bash" label="/bin/bash" />
<el-option value="sh" label="/bin/sh" /> <el-option value="sh" label="/bin/sh" />
</el-select> </el-select>
<el-input
clearable
v-else
style="width: calc(100% - 100px)"
v-model="dialogData.rowData!.command"
/>
</el-form-item> </el-form-item>
</el-col>
</el-row>
</el-card>
<div v-if="!dialogData.rowData!.inContainer">
<el-card>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="$t('commons.table.user')"
prop="user"
:rules="Rules.requiredSelect"
>
<el-select filterable v-model="dialogData.rowData!.user">
<div v-for="item in userOptions" :key="item">
<el-option :value="item" :label="item" />
</div>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="$t('cronjob.executor')"
prop="executor"
:rules="Rules.requiredSelect"
>
<el-checkbox border v-model="dialogData.rowData!.isExecutorCustom">
{{ $t('container.custom') }}
</el-checkbox>
<el-select
v-if="!dialogData.rowData!.isExecutorCustom"
style="width: calc(100% - 100px)"
v-model="dialogData.rowData!.executor"
>
<el-option value="bash" label="bash" />
<el-option value="python" label="python" />
<el-option value="sh" label="sh" />
</el-select>
<el-input
clearable
v-else
style="width: calc(100% - 100px)"
v-model="dialogData.rowData!.executor"
/>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-form-item v-if="hasScript()" :label="$t('cronjob.shellContent')" prop="script"> <el-form-item :label="$t('cronjob.shellContent')" prop="script" class="mt-5">
<el-input clearable type="textarea" :rows="5" v-model="dialogData.rowData!.script" /> <el-radio-group v-model="dialogData.rowData!.scriptMode">
<el-radio value="input">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="select">{{ $t('container.pathSelect') }}</el-radio>
</el-radio-group>
<CodemirrorPro
v-if="dialogData.rowData!.scriptMode=== 'input'"
v-model="dialogData.rowData!.script"
placeholder="#Define or paste the content of your shell file here"
mode="javascript"
:heightDiff="400"
/>
<el-input
v-if="dialogData.rowData!.scriptMode=== 'select'"
:placeholder="$t('commons.example') + '/tmp/test.sh'"
v-model="dialogData.rowData!.script"
>
<template #prepend>
<FileList @choose="loadScriptDir" :dir="false"></FileList>
</template>
</el-input>
</el-form-item> </el-form-item>
</div>
<el-form-item :label="$t('cronjob.shellContent')" v-else prop="script" class="mt-5">
<CodemirrorPro
v-if="dialogData.rowData!.scriptMode=== 'input'"
v-model="dialogData.rowData!.script"
placeholder="#Define or paste the content of your shell file here"
mode="javascript"
:heightDiff="400"
/>
</el-form-item>
</div>
<el-form-item <el-form-item
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'" v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
@ -383,6 +485,7 @@ import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
import { Cronjob } from '@/api/interface/cronjob'; import { Cronjob } from '@/api/interface/cronjob';
import { addCronjob, editCronjob, loadNextHandle } from '@/api/modules/cronjob'; import { addCronjob, editCronjob, loadNextHandle } from '@/api/modules/cronjob';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import { listDbItems } from '@/api/modules/database'; import { listDbItems } from '@/api/modules/database';
import { GetWebsiteOptions } from '@/api/modules/website'; import { GetWebsiteOptions } from '@/api/modules/website';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
@ -398,6 +501,7 @@ import {
transSpecToObj, transSpecToObj,
weekOptions, weekOptions,
} from './../helper'; } from './../helper';
import { loadUsers } from '@/api/modules/toolbox';
const router = useRouter(); const router = useRouter();
interface DialogProps { interface DialogProps {
@ -428,6 +532,7 @@ const acceptParams = (params: DialogProps): void => {
dialogData.value.rowData.specs = dialogData.value.rowData.specs || []; dialogData.value.rowData.specs = dialogData.value.rowData.specs || [];
if (dialogData.value.title === 'create') { if (dialogData.value.title === 'create') {
changeType(); changeType();
dialogData.value.rowData.scriptMode = 'input';
dialogData.value.rowData.dbType = 'mysql'; dialogData.value.rowData.dbType = 'mysql';
dialogData.value.rowData.downloadAccountID = 1; dialogData.value.rowData.downloadAccountID = 1;
} }
@ -442,7 +547,14 @@ const acceptParams = (params: DialogProps): void => {
dialogData.value.rowData!.isCustom = dialogData.value.rowData!.isCustom =
dialogData.value.rowData!.command !== 'sh' && dialogData.value.rowData!.command !== 'sh' &&
dialogData.value.rowData!.command !== 'bash' && dialogData.value.rowData!.command !== 'bash' &&
dialogData.value.rowData!.command === 'ash'; dialogData.value.rowData!.command !== 'ash';
dialogData.value.rowData!.executor = dialogData.value.rowData!.executor || 'bash';
dialogData.value.rowData!.isCustom =
dialogData.value.rowData!.executor !== 'sh' &&
dialogData.value.rowData!.executor !== 'bash' &&
dialogData.value.rowData!.executor !== 'python' &&
dialogData.value.rowData!.executor !== 'python3';
title.value = i18n.global.t('cronjob.' + dialogData.value.title); title.value = i18n.global.t('cronjob.' + dialogData.value.title);
if (dialogData.value?.rowData?.exclusionRules) { if (dialogData.value?.rowData?.exclusionRules) {
@ -454,6 +566,7 @@ const acceptParams = (params: DialogProps): void => {
drawerVisible.value = true; drawerVisible.value = true;
loadBackups(); loadBackups();
loadAppInstalls(); loadAppInstalls();
loadShellUsers();
loadWebsites(); loadWebsites();
loadContainers(); loadContainers();
if (dialogData.value.rowData?.dbType) { if (dialogData.value.rowData?.dbType) {
@ -477,6 +590,7 @@ const websiteOptions = ref([]);
const backupOptions = ref([]); const backupOptions = ref([]);
const accountOptions = ref([]); const accountOptions = ref([]);
const appOptions = ref([]); const appOptions = ref([]);
const userOptions = ref([]);
const dbInfo = reactive({ const dbInfo = reactive({
isExist: false, isExist: false,
@ -485,6 +599,14 @@ const dbInfo = reactive({
dbs: [] as Array<Database.DbItem>, dbs: [] as Array<Database.DbItem>,
}); });
const verifyScript = (rule: any, value: any, callback: any) => {
if (!dialogData.value.rowData!.script || dialogData.value.rowData!.script.length === 0) {
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
return;
}
callback();
};
const verifySpec = (rule: any, value: any, callback: any) => { const verifySpec = (rule: any, value: any, callback: any) => {
if (dialogData.value.rowData!.specObjs.length === 0) { if (dialogData.value.rowData!.specObjs.length === 0) {
callback(new Error(i18n.global.t('commons.rule.requiredInput'))); callback(new Error(i18n.global.t('commons.rule.requiredInput')));
@ -583,7 +705,7 @@ const rules = reactive({
{ validator: verifySpec, trigger: 'change', required: true }, { validator: verifySpec, trigger: 'change', required: true },
], ],
script: [Rules.requiredInput], script: [{ validator: verifyScript, trigger: 'blur', required: true }],
website: [Rules.requiredSelect], website: [Rules.requiredSelect],
dbName: [Rules.requiredSelect], dbName: [Rules.requiredSelect],
url: [Rules.requiredInput], url: [Rules.requiredInput],
@ -600,6 +722,10 @@ const loadDir = async (path: string) => {
dialogData.value.rowData!.sourceDir = path; dialogData.value.rowData!.sourceDir = path;
}; };
const loadScriptDir = async (path: string) => {
dialogData.value.rowData!.script = path;
};
const hasDay = (item: any) => { const hasDay = (item: any) => {
return item.specType === 'perMonth' || item.specType === 'perNDay'; return item.specType === 'perMonth' || item.specType === 'perNDay';
}; };
@ -724,6 +850,11 @@ const changeAccount = async () => {
} }
}; };
const loadShellUsers = async () => {
const res = await loadUsers();
userOptions.value = res.data || [];
};
const loadAppInstalls = async () => { const loadAppInstalls = async () => {
const res = await ListAppInstalled(); const res = await ListAppInstalled();
appOptions.value = res.data || []; appOptions.value = res.data || [];