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

feat: 计划任务增加每隔 n 秒执行 (#1132)

This commit is contained in:
ssongliu 2023-05-24 18:36:41 +08:00 committed by GitHub
parent 93b03c7212
commit f65f0d86aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 22 deletions

View File

@ -10,6 +10,7 @@ type CronjobCreate struct {
Day int `json:"day" validate:"number"` Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"` Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"` Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"`
Script string `json:"script"` Script string `json:"script"`
Website string `json:"website"` Website string `json:"website"`
@ -30,6 +31,7 @@ type CronjobUpdate struct {
Day int `json:"day" validate:"number"` Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"` Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"` Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"`
Script string `json:"script"` Script string `json:"script"`
Website string `json:"website"` Website string `json:"website"`
@ -71,6 +73,7 @@ type CronjobInfo struct {
Day int `json:"day"` Day int `json:"day"`
Hour int `json:"hour"` Hour int `json:"hour"`
Minute int `json:"minute"` Minute int `json:"minute"`
Second int `json:"second"`
Script string `json:"script"` Script string `json:"script"`
Website string `json:"website"` Website string `json:"website"`

View File

@ -13,6 +13,7 @@ type Cronjob struct {
Day uint64 `gorm:"type:decimal" json:"day"` Day uint64 `gorm:"type:decimal" json:"day"`
Hour uint64 `gorm:"type:decimal" json:"hour"` Hour uint64 `gorm:"type:decimal" json:"hour"`
Minute uint64 `gorm:"type:decimal" json:"minute"` Minute uint64 `gorm:"type:decimal" json:"minute"`
Second uint64 `gorm:"type:decimal" json:"second"`
Script string `gorm:"longtext" json:"script"` Script string `gorm:"longtext" json:"script"`
Website string `gorm:"type:varchar(64)" json:"website"` Website string `gorm:"type:varchar(64)" json:"website"`

View File

@ -322,6 +322,8 @@ func loadSpec(cronjob model.Cronjob) string {
return fmt.Sprintf("%v * * * *", cronjob.Minute) return fmt.Sprintf("%v * * * *", cronjob.Minute)
case "perNMinute": case "perNMinute":
return fmt.Sprintf("@every %vm", cronjob.Minute) return fmt.Sprintf("@every %vm", cronjob.Minute)
case "perNSecond":
return fmt.Sprintf("@every %vs", cronjob.Second)
default: default:
return "" return ""
} }

View File

@ -29,6 +29,7 @@ func Init() {
migrations.UpdateTableSetting, migrations.UpdateTableSetting,
migrations.UpdateTableAppDetail, migrations.UpdateTableAppDetail,
migrations.AddBindAndAllowIPs, migrations.AddBindAndAllowIPs,
migrations.UpdateCronjobWithSecond,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -337,7 +337,7 @@ var UpdateTableAppDetail = &gormigrate.Migration{
} }
var AddBindAndAllowIPs = &gormigrate.Migration{ var AddBindAndAllowIPs = &gormigrate.Migration{
ID: "20230414-add-bind-and-allow", ID: "20230517-add-bind-and-allow",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "BindDomain", Value: ""}).Error; err != nil { if err := tx.Create(&model.Setting{Key: "BindDomain", Value: ""}).Error; err != nil {
return err return err
@ -354,3 +354,13 @@ var AddBindAndAllowIPs = &gormigrate.Migration{
return nil return nil
}, },
} }
var UpdateCronjobWithSecond = &gormigrate.Migration{
ID: "20200524-update-table-cronjob",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
return err
}
return nil
},
}

View File

@ -10,6 +10,7 @@ export namespace Cronjob {
day: number; day: number;
hour: number; hour: number;
minute: number; minute: number;
second: number;
script: string; script: string;
website: string; website: string;
@ -31,6 +32,7 @@ export namespace Cronjob {
day: number; day: number;
hour: number; hour: number;
minute: number; minute: number;
second: number;
script: string; script: string;
website: string; website: string;
@ -49,6 +51,7 @@ export namespace Cronjob {
day: number; day: number;
hour: number; hour: number;
minute: number; minute: number;
second: number;
script: string; script: string;
website: string; website: string;

View File

@ -651,12 +651,14 @@ const message = {
perDay: 'Every days', perDay: 'Every days',
perNHour: 'Every N hours', perNHour: 'Every N hours',
perNMinute: 'Every N minutes', perNMinute: 'Every N minutes',
perNSecond: 'Every N seconds',
per: 'Every ', per: 'Every ',
handle: 'Handle', handle: 'Handle',
day: 'Day', day: 'Day',
day1: 'Day', day1: 'Day',
hour: ' Hour', hour: ' Hour',
minute: ' Minute', minute: ' Minute',
second: ' Second',
monday: 'Monday', monday: 'Monday',
tuesday: 'Tuesday', tuesday: 'Tuesday',
wednesday: 'Wednesday', wednesday: 'Wednesday',

View File

@ -657,12 +657,14 @@ const message = {
perDay: '每天', perDay: '每天',
perNHour: '每隔 N ', perNHour: '每隔 N ',
perNMinute: '每隔 N 分钟', perNMinute: '每隔 N 分钟',
perNSecond: '每隔 N ',
per: '每隔', per: '每隔',
handle: '执行', handle: '执行',
day: '日', day: '日',
day1: '天', day1: '天',
hour: '小时', hour: '小时',
minute: '分钟', minute: '分钟',
second: '秒',
monday: '周一', monday: '周一',
tuesday: '周二', tuesday: '周二',
wednesday: '周三', wednesday: '周三',

View File

@ -95,6 +95,7 @@
</span> </span>
<span v-if="row.specType === 'perHour'">{{ loadZero(row.minute) }}</span> <span v-if="row.specType === 'perHour'">{{ loadZero(row.minute) }}</span>
<span v-if="row.specType === 'perNMinute'">{{ row.minute }}{{ $t('cronjob.minute') }}</span> <span v-if="row.specType === 'perNMinute'">{{ row.minute }}{{ $t('cronjob.minute') }}</span>
<span v-if="row.specType === 'perNSecond'">{{ row.second }}{{ $t('cronjob.second') }}</span>
{{ $t('cronjob.handle') }} {{ $t('cronjob.handle') }}
</template> </template>
</el-table-column> </el-table-column>
@ -227,6 +228,7 @@ const onOpenDialog = async (
day: 3, day: 3,
hour: 1, hour: 1,
minute: 30, minute: 30,
second: 30,
keepLocal: true, keepLocal: true,
retainCopies: 7, retainCopies: 7,
}, },

View File

@ -9,7 +9,7 @@
<el-form-item :label="$t('cronjob.taskType')" prop="type"> <el-form-item :label="$t('cronjob.taskType')" prop="type">
<el-select <el-select
v-if="dialogData.title === 'create'" v-if="dialogData.title === 'create'"
style="width: 100%" class="selectClass"
@change="changeType" @change="changeType"
v-model="dialogData.rowData!.type" v-model="dialogData.rowData!.type"
> >
@ -27,7 +27,6 @@
<el-form-item :label="$t('cronjob.taskName')" prop="name"> <el-form-item :label="$t('cronjob.taskName')" prop="name">
<el-input <el-input
:disabled="dialogData.title === 'edit'" :disabled="dialogData.title === 'edit'"
style="width: 100%"
clearable clearable
v-model.trim="dialogData.rowData!.name" v-model.trim="dialogData.rowData!.name"
/> />
@ -44,7 +43,7 @@
</el-select> </el-select>
<el-select <el-select
v-if="dialogData.rowData!.specType === 'perWeek'" v-if="dialogData.rowData!.specType === 'perWeek'"
style="width: 12%; margin-left: 20px" class="specClass"
v-model="dialogData.rowData!.week" v-model="dialogData.rowData!.week"
> >
<el-option <el-option
@ -54,28 +53,30 @@
:label="item.label" :label="item.label"
/> />
</el-select> </el-select>
<el-input <el-input v-if="hasDay()" class="specClass" v-model.number="dialogData.rowData!.day">
v-if="dialogData.rowData!.specType === 'perMonth' || dialogData.rowData!.specType === 'perNDay'"
style="width: 20%; margin-left: 20px"
v-model.number="dialogData.rowData!.day"
>
<template #append>{{ $t('cronjob.day') }}</template> <template #append>{{ $t('cronjob.day') }}</template>
</el-input> </el-input>
<el-input <el-input v-if="hasHour()" class="specClass" v-model.number="dialogData.rowData!.hour">
v-if="dialogData.rowData!.specType !== 'perHour' && dialogData.rowData!.specType !== 'perNMinute'"
style="width: 20%; margin-left: 20px"
v-model.number="dialogData.rowData!.hour"
>
<template #append>{{ $t('cronjob.hour') }}</template> <template #append>{{ $t('cronjob.hour') }}</template>
</el-input> </el-input>
<el-input style="width: 20%; margin-left: 20px" v-model.number="dialogData.rowData!.minute"> <el-input
v-if="dialogData.rowData!.specType !== 'perNSecond'"
class="specClass"
v-model.number="dialogData.rowData!.minute"
>
<template #append>{{ $t('cronjob.minute') }}</template> <template #append>{{ $t('cronjob.minute') }}</template>
</el-input> </el-input>
<el-input
v-if="dialogData.rowData!.specType === 'perNSecond'"
class="specClass"
v-model.number="dialogData.rowData!.second"
>
<template #append>{{ $t('cronjob.second') }}</template>
</el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="hasScript()" :label="$t('cronjob.shellContent')" prop="script"> <el-form-item v-if="hasScript()" :label="$t('cronjob.shellContent')" prop="script">
<el-input <el-input
style="width: 100%"
clearable clearable
type="textarea" type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }" :autosize="{ minRows: 3, maxRows: 6 }"
@ -88,7 +89,7 @@
:label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')" :label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')"
prop="website" prop="website"
> >
<el-select style="width: 100%" v-model="dialogData.rowData!.website"> <el-select class="selectClass" v-model="dialogData.rowData!.website">
<el-option :label="$t('commons.table.all')" value="all" /> <el-option :label="$t('commons.table.all')" value="all" />
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" /> <el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
</el-select> </el-select>
@ -99,7 +100,7 @@
<div v-if="dialogData.rowData!.type === 'database'"> <div v-if="dialogData.rowData!.type === 'database'">
<el-form-item :label="$t('cronjob.database')" prop="dbName"> <el-form-item :label="$t('cronjob.database')" prop="dbName">
<el-select style="width: 100%" clearable v-model="dialogData.rowData!.dbName"> <el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
<el-option :label="$t('commons.table.all')" value="all" /> <el-option :label="$t('commons.table.all')" value="all" />
<el-option v-for="item in mysqlInfo.dbNames" :key="item" :label="item" :value="item" /> <el-option v-for="item in mysqlInfo.dbNames" :key="item" :label="item" :value="item" />
</el-select> </el-select>
@ -111,7 +112,7 @@
:label="$t('cronjob.sourceDir')" :label="$t('cronjob.sourceDir')"
prop="sourceDir" prop="sourceDir"
> >
<el-input style="width: 100%" v-model="dialogData.rowData!.sourceDir"> <el-input v-model="dialogData.rowData!.sourceDir">
<template #prepend> <template #prepend>
<FileList @choose="loadDir" :dir="true"></FileList> <FileList @choose="loadDir" :dir="true"></FileList>
</template> </template>
@ -120,7 +121,7 @@
<div v-if="isBackup()"> <div v-if="isBackup()">
<el-form-item :label="$t('cronjob.target')" prop="targetDirID"> <el-form-item :label="$t('cronjob.target')" prop="targetDirID">
<el-select style="width: 100%" v-model="dialogData.rowData!.targetDirID"> <el-select class="selectClass" v-model="dialogData.rowData!.targetDirID">
<el-option <el-option
v-for="item in backupOptions" v-for="item in backupOptions"
:key="item.label" :key="item.label"
@ -148,7 +149,7 @@
</el-form-item> </el-form-item>
<el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url')" prop="url"> <el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url')" prop="url">
<el-input style="width: 100%" clearable v-model.trim="dialogData.rowData!.url" /> <el-input clearable v-model.trim="dialogData.rowData!.url" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@ -157,7 +158,6 @@
prop="exclusionRules" prop="exclusionRules"
> >
<el-input <el-input
style="width: 100%"
type="textarea" type="textarea"
:placeholder="$t('cronjob.rulesHelper')" :placeholder="$t('cronjob.rulesHelper')"
:autosize="{ minRows: 3, maxRows: 6 }" :autosize="{ minRows: 3, maxRows: 6 }"
@ -284,6 +284,11 @@ const varifySpec = (rule: any, value: any, callback: any) => {
callback(new Error(i18n.global.t('cronjob.cronSpecRule'))); callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
} }
break; break;
case 'perNSecond':
if (!Number.isInteger(dialogData.value.rowData!.second)) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
}
break;
} }
callback(); callback();
}; };
@ -296,6 +301,7 @@ const specOptions = [
{ label: i18n.global.t('cronjob.perNDay'), value: 'perNDay' }, { label: i18n.global.t('cronjob.perNDay'), value: 'perNDay' },
{ label: i18n.global.t('cronjob.perNHour'), value: 'perNHour' }, { label: i18n.global.t('cronjob.perNHour'), value: 'perNHour' },
{ label: i18n.global.t('cronjob.perNMinute'), value: 'perNMinute' }, { label: i18n.global.t('cronjob.perNMinute'), value: 'perNMinute' },
{ label: i18n.global.t('cronjob.perNSecond'), value: 'perNSecond' },
]; ];
const weekOptions = [ const weekOptions = [
{ label: i18n.global.t('cronjob.monday'), value: 1 }, { label: i18n.global.t('cronjob.monday'), value: 1 },
@ -335,6 +341,17 @@ const loadDir = async (path: string) => {
dialogData.value.rowData!.sourceDir = path; dialogData.value.rowData!.sourceDir = path;
}; };
const hasDay = () => {
return dialogData.value.rowData!.specType === 'perMonth' || dialogData.value.rowData!.specType === 'perNDay';
};
const hasHour = () => {
return (
dialogData.value.rowData!.specType !== 'perHour' &&
dialogData.value.rowData!.specType !== 'perNMinute' &&
dialogData.value.rowData!.specType !== 'perNSecond'
);
};
const changeType = () => { const changeType = () => {
switch (dialogData.value.rowData!.type) { switch (dialogData.value.rowData!.type) {
case 'shell': case 'shell':
@ -430,6 +447,8 @@ function checkScript() {
return row.hour > 0 && row.hour < 8784 && row.minute >= 0 && row.minute < 60; return row.hour > 0 && row.hour < 8784 && row.minute >= 0 && row.minute < 60;
case 'perNMinute': case 'perNMinute':
return row.minute > 0 && row.minute < 527040; return row.minute > 0 && row.minute < 527040;
case 'perNSecond':
return row.second > 0 && row.second < 31622400;
} }
} }
@ -438,6 +457,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
dialogData.value.rowData.day = Number(dialogData.value.rowData.day); dialogData.value.rowData.day = Number(dialogData.value.rowData.day);
dialogData.value.rowData.hour = Number(dialogData.value.rowData.hour); dialogData.value.rowData.hour = Number(dialogData.value.rowData.hour);
dialogData.value.rowData.minute = Number(dialogData.value.rowData.minute); dialogData.value.rowData.minute = Number(dialogData.value.rowData.minute);
dialogData.value.rowData.second = Number(dialogData.value.rowData.second);
if (!checkScript()) { if (!checkScript()) {
MsgError(i18n.global.t('cronjob.cronSpecHelper')); MsgError(i18n.global.t('cronjob.cronSpecHelper'));
return; return;
@ -463,3 +483,12 @@ defineExpose({
acceptParams, acceptParams,
}); });
</script> </script>
<style scoped lang="scss">
.specClass {
width: 20%;
margin-left: 20px;
}
.selectClass {
width: 100%;
}
</style>