feat: Modify the way of creating cronjob (#8403)

This commit is contained in:
ssongliu 2025-04-16 14:00:17 +08:00 committed by GitHub
parent c59105f050
commit d10d83b648
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 982 additions and 763 deletions

View file

@ -12,14 +12,14 @@ import (
// @Tags Cronjob
// @Summary Create cronjob
// @Accept json
// @Param request body dto.CronjobCreate true "request"
// @Param request body dto.CronjobOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs [post]
// @x-panel-log {"bodyKeys":["type","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建计划任务 [type][name]","formatEN":"create cronjob [type][name]"}
func (b *BaseApi) CreateCronjob(c *gin.Context) {
var req dto.CronjobCreate
var req dto.CronjobOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
@ -31,6 +31,28 @@ func (b *BaseApi) CreateCronjob(c *gin.Context) {
helper.SuccessWithOutData(c)
}
// @Tags Cronjob
// @Summary Load cronjob info
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/load/info [post]
func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := cronjobService.LoadInfo(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Cronjob
// @Summary Load cronjob spec time
// @Accept json
@ -174,14 +196,14 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) {
// @Tags Cronjob
// @Summary Update cronjob
// @Accept json
// @Param request body dto.CronjobUpdate true "request"
// @Param request body dto.CronjobOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"}
func (b *BaseApi) UpdateCronjob(c *gin.Context) {
var req dto.CronjobUpdate
var req dto.CronjobOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

View file

@ -15,7 +15,8 @@ type CronjobSpec struct {
Spec string `json:"spec" validate:"required"`
}
type CronjobCreate struct {
type CronjobOperate struct {
ID uint `json:"id"`
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
SpecCustom bool `json:"specCustom"`
@ -48,40 +49,6 @@ type CronjobCreate struct {
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
SpecCustom bool `json:"specCustom"`
Spec string `json:"spec" validate:"required"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
User string `json:"user"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
SourceAccountIDs string `json:"sourceAccountIDs"`
DownloadAccountID uint `json:"downloadAccountID"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
RetryTimes int `json:"retryTimes" validate:"number,min=0"`
Timeout uint `json:"timeout" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdateStatus struct {
ID uint `json:"id" validate:"required"`
Status string `json:"status" validate:"required"`

View file

@ -27,16 +27,17 @@ type CronjobService struct{}
type ICronjobService interface {
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
Create(cronjobDto dto.CronjobCreate) error
Create(cronjobDto dto.CronjobOperate) error
LoadNextHandle(spec string) ([]string, error)
HandleOnce(id uint) error
Update(id uint, req dto.CronjobUpdate) error
Update(id uint, req dto.CronjobOperate) error
UpdateStatus(id uint, status string) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
LoadRecordLog(req dto.OperateByID) string
}
@ -75,6 +76,25 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error) {
cronjob, err := cronjobRepo.Get(repo.WithByID(req.ID))
var item dto.CronjobOperate
if err := copier.Copy(&item, &cronjob); err != nil {
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
} else {
item.AlertCount = 0
}
return &item, err
}
func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interface{}, error) {
total, records, err := cronjobRepo.PageRecords(
search.Page,
@ -216,7 +236,7 @@ func (u *CronjobService) HandleOnce(id uint) error {
return nil
}
func (u *CronjobService) Create(req dto.CronjobCreate) error {
func (u *CronjobService) Create(req dto.CronjobOperate) error {
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
if cronjob.ID != 0 {
return buserr.New("ErrRecordExist")
@ -306,7 +326,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
return nil
}
func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
var cronjob model.Cronjob
if err := copier.Copy(&cronjob, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
@ -353,6 +373,8 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["source_account_ids"] = req.SourceAccountIDs
upMap["download_account_id"] = req.DownloadAccountID
upMap["retain_copies"] = req.RetainCopies
upMap["retry_times"] = req.RetryTimes
upMap["timeout"] = req.Timeout
upMap["secret"] = req.Secret
err = cronjobRepo.Update(id, upMap)
if err != nil {

View file

@ -13,6 +13,7 @@ func (s *CronjobRouter) InitRouter(Router *gin.RouterGroup) {
{
cmdRouter.POST("", baseApi.CreateCronjob)
cmdRouter.POST("/next", baseApi.LoadNextHandle)
cmdRouter.POST("/load/info", baseApi.LoadCronjobInfo)
cmdRouter.POST("/del", baseApi.DeleteCronjob)
cmdRouter.POST("/update", baseApi.UpdateCronjob)
cmdRouter.POST("/status", baseApi.UpdateCronjobStatus)

View file

@ -111,10 +111,10 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd.Dir = c.workDir
}
if c.timeout != 0 {
if err := cmd.Start(); err != nil {
return "", err
}
if c.timeout != 0 {
done := make(chan error, 1)
go func() {
done <- cmd.Wait()

View file

@ -111,10 +111,10 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd.Dir = c.workDir
}
if c.timeout != 0 {
if err := cmd.Start(); err != nil {
return "", err
}
if c.timeout != 0 {
done := make(chan error, 1)
go func() {
done <- cmd.Wait()

View file

@ -36,6 +36,10 @@ export namespace Cronjob {
sourceAccountItems: Array<number>;
retainCopies: number;
retryTimes: number;
timeout: number;
timeoutItem: number;
timeoutUint: string;
status: string;
secret: string;
hasAlert: boolean;
@ -45,25 +49,41 @@ export namespace Cronjob {
export interface Item {
val: string;
}
export interface CronjobCreate {
export interface CronjobOperate {
id: number;
name: string;
type: string;
specCustom: boolean;
spec: string;
specs: Array<string>;
specObjs: Array<SpecObj>;
script: string;
appID: string;
website: string;
exclusionRules: string;
dbType: string;
dbName: string;
url: string;
isDir: boolean;
sourceDir: string;
//shell
executor: string;
scriptMode: string;
script: string;
command: string;
containerName: string;
user: string;
sourceAccountIDs: string;
downloadAccountID: number;
retainCopies: number;
retryTimes: number;
timeout: number;
secret: string;
alertCount: number;
alertTitle: string;
}
export interface SpecObj {
specType: string;
@ -73,24 +93,6 @@ export namespace Cronjob {
minute: number;
second: number;
}
export interface CronjobUpdate {
id: number;
specCustom: boolean;
spec: string;
script: string;
website: string;
exclusionRules: string;
dbType: string;
dbName: string;
url: string;
sourceDir: string;
sourceAccountIDs: string;
downloadAccountID: number;
retainCopies: number;
secret: string;
}
export interface CronjobDelete {
ids: Array<number>;
cleanData: boolean;

View file

@ -11,15 +11,19 @@ export const loadNextHandle = (spec: string) => {
return http.post<Array<String>>(`/cronjobs/next`, { spec: spec });
};
export const loadCronjobInfo = (id: number) => {
return http.post<Cronjob.CronjobOperate>(`/cronjobs/load/info`, { id: id });
};
export const getRecordLog = (id: number) => {
return http.post<string>(`/cronjobs/records/log`, { id: id });
};
export const addCronjob = (params: Cronjob.CronjobCreate) => {
return http.post<Cronjob.CronjobCreate>(`/cronjobs`, params);
export const addCronjob = (params: Cronjob.CronjobOperate) => {
return http.post<Cronjob.CronjobOperate>(`/cronjobs`, params);
};
export const editCronjob = (params: Cronjob.CronjobUpdate) => {
export const editCronjob = (params: Cronjob.CronjobOperate) => {
return http.post(`/cronjobs/update`, params);
};

View file

@ -966,8 +966,7 @@ const message = {
ntp_helper: 'You can configure the NTP server on the Quick Setup page of the Toolbox.',
app: 'Backup App',
website: 'Backup Website',
rulesHelper:
'When there are multiple compression exclusion rules, they need to be displayed with line breaks. For example: \n*.log \n*.sql',
rulesHelper: 'When multiple compression exclusion rules exist, separate them with [,]. Example: *.log,*.sql',
lastRecordTime: 'Last Execution',
all: 'All',
failedRecord: 'Failed Records',
@ -995,6 +994,9 @@ const message = {
url: 'URL Address',
targetHelper: 'Backup accounts are maintained in panel settings.',
retainCopies: 'Retain Copies',
retryTimes: 'Retry Attempts',
timeout: 'Timeout',
retryTimesHelper: '0 means no retry after failure',
retainCopiesHelper: 'Number of copies to retain for execution records and logs',
retainCopiesHelper1: 'Number of copies to retain for backup files',
retainCopiesUnit: ' copies (View)',

View file

@ -935,7 +935,7 @@ const message = {
ntp_helper: 'ツールボックスのクイックセットアップページでNTPサーバーを構成できます',
app: 'バックアップアプリ',
website: 'バックアップウェブサイト',
rulesHelper: '圧縮除外ルールがある場合ラインブレークで表示する必要がありますたとえば n*.log n*.sql',
rulesHelper: '複数の圧縮除外ルールがある場合[,]で区切ります: *.log,*.sql',
lastRecordTime: '最後の実行時間',
all: '全て',
failedRecord: '失敗記録',
@ -962,6 +962,9 @@ const message = {
url: 'URLアドレス',
targetHelper: 'バックアップアカウントはパネル設定で維持されます',
retainCopies: '記録を保持します',
retryTimes: 'リトライ回数',
timeout: 'タイムアウト',
retryTimesHelper: '0は失敗後リトライしないことを意味します',
retainCopiesHelper: '実行記録とログのために保持するコピーの数',
retainCopiesHelper1: 'バックアップファイル用に保持するコピーの数',
retainCopiesUnit: 'コピー表示',

View file

@ -925,7 +925,7 @@ const message = {
ntp_helper: 'Toolbox 빠른 설정 페이지에서 NTP 서버를 구성할 있습니다.',
app: '백업 ',
website: '백업 웹사이트',
rulesHelper: '여러 개의 압축 제외 규칙이 경우 바꿈으로 표시해야 합니다. 예시:\n*.log \n*.sql',
rulesHelper: '여러 개의 압축 제외 규칙이 경우 [,] 구분하세요. : *.log,*.sql',
lastRecordTime: '마지막 실행 시간',
all: '전체',
failedRecord: '실패한 레코드',
@ -950,6 +950,9 @@ const message = {
url: 'URL 주소',
targetHelper: '백업 계정은 패널 설정에서 관리됩니다.',
retainCopies: '기록 보관',
retryTimes: '재시도 횟수',
timeout: '타임아웃',
retryTimesHelper: '0 실패 재시도 함을 의미합니다',
retainCopiesHelper: '실행 기록과 로그에 대해 보관할 복사본 ',
retainCopiesHelper1: '백업 파일에 대해 보관할 복사본 ',
retainCopiesUnit: ' (보기)',

View file

@ -958,7 +958,7 @@ const message = {
app: 'Aplikasi sandaran',
website: 'Laman web sandaran',
rulesHelper:
'Apabila terdapat pelbagai peraturan pengecualian mampatan, ia perlu dipaparkan dengan pemecahan baris. Sebagai contoh,\n*.log \n*.sql',
'Apabila terdapat pelbagai peraturan pengecualian mampatan, gunakan [,] sebagai pemisah. Contoh: *.log,*.sql',
lastRecordTime: 'Waktu pelaksanaan terakhir',
all: 'Semua',
failedRecord: 'Rekod kegagalan',
@ -985,6 +985,9 @@ const message = {
url: 'Alamat URL',
targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.',
retainCopies: 'Simpan salinan',
retryTimes: 'Bilangan Cubaan Semula',
timeout: 'Masa Tamat',
retryTimesHelper: '0 bermaksud tiada cubaan semula selepas gagal',
retainCopiesHelper: 'Bilangan salinan untuk menyimpan rekod pelaksanaan dan log',
retainCopiesHelper1: 'Bilangan salinan untuk menyimpan fail sandaran',
retainCopiesUnit: ' salinan (Lihat)',

View file

@ -953,7 +953,7 @@ const message = {
app: 'Backup de app',
website: 'Backup de site',
rulesHelper:
'Quando houver múltiplas regras de exclusão de compressão, elas devem ser exibidas com quebras de linha. Por exemplo,\n*.log\n*.sql',
'Quando existem múltiplas regras de exclusão de compactação, separe-as com [,]. Exemplo: *.log,*.sql',
lastRecordTime: 'Última execução',
all: 'Todos',
failedRecord: 'Registros de falha',
@ -980,6 +980,9 @@ const message = {
url: 'Endereço URL',
targetHelper: 'As contas de backup são mantidas nas configurações do painel.',
retainCopies: 'Manter cópias',
retryTimes: 'Tentativas de Repetição',
timeout: 'Tempo Limite',
retryTimesHelper: '0 significa não repetir após falha',
retainCopiesHelper: 'Número de cópias a serem mantidas para registros de execução e logs',
retainCopiesHelper1: 'Número de cópias a serem mantidas para arquivos de backup',
retainCopiesUnit: ' cópias (Visualizar)',

View file

@ -952,8 +952,7 @@ const message = {
ntp_helper: 'Вы можете настроить NTP сервер на странице Быстрой настройки в Инструментах.',
app: 'Резервное копирование приложения',
website: 'Резервное копирование сайта',
rulesHelper:
'Когда существует несколько правил исключения сжатия, они должны отображаться с переносом строки. Например,\n*.log \n*.sql',
rulesHelper: 'При наличии нескольких правил исключения сжатия разделяйте их [,]. Пример: *.log,*.sql',
lastRecordTime: 'Время последнего выполнения',
all: 'Все',
failedRecord: 'Неудачные записи',
@ -980,6 +979,9 @@ const message = {
url: 'URL-адрес',
targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.',
retainCopies: 'Сохранять записи',
retryTimes: 'Количество повторов',
timeout: 'Таймаут',
retryTimesHelper: '0 означает отсутствие повторов после сбоя',
retainCopiesHelper: 'Количество копий для сохранения записей выполнения и логов',
retainCopiesHelper1: 'Количество копий для сохранения файлов резервных копий',
retainCopiesUnit: ' копий (Просмотр)',

View file

@ -920,7 +920,7 @@ const message = {
ntp_helper: '您可以在工具箱的快速設定頁面配置 NTP 伺服器',
app: '備份應用',
website: '備份網站',
rulesHelper: '當存在多個壓縮排除規則時需要換行顯示\n*.log \n*.sql',
rulesHelper: '當存在多個壓縮排除規則時使用[,]分隔*.log,*.sql',
lastRecordTime: '上次執行情況',
database: '備份數據庫',
missBackupAccount: '未能找到備份賬號',
@ -943,6 +943,9 @@ const message = {
url: 'URL 地址',
targetHelper: '備份賬號可在面板設置中維護',
retainCopies: '保留份數',
retryTimes: '失敗重試次數',
timeout: '逾時時間',
retryTimesHelper: '為0表示失敗後不重試',
retainCopiesHelper: '執行記錄及日誌保留份数',
retainCopiesHelper1: '備份文件保留份数',
retainCopiesUnit: ' (查看)',

View file

@ -918,7 +918,7 @@ const message = {
ntp_helper: '您可以在工具箱的快速设置页面配置 NTP 服务器',
app: '备份应用',
website: '备份网站',
rulesHelper: '当存在多个压缩排除规则时需要换行显示\n*.log \n*.sql',
rulesHelper: '当存在多个压缩排除规则时使用 [,] 分隔*.log,*.sql',
lastRecordTime: '上次执行情况',
database: '备份数据库',
missBackupAccount: '未能找到备份账号',
@ -941,6 +941,9 @@ const message = {
url: 'URL 地址',
targetHelper: '备份账号可在面板设置中维护',
retainCopies: '保留份数',
retryTimes: '失败重试次数',
timeout: '超时时间',
retryTimesHelper: ' 0 表示失败后不重试',
retainCopiesHelper: '执行记录及日志保留份数',
retainCopiesHelper1: '备份文件保留份数',
retainCopiesUnit: ' (查看)',

View file

@ -76,9 +76,15 @@ router.afterEach((to) => {
notMathParam = false;
}
if (notMathParam) {
if (to.meta.activeMenu === '/cronjobs' && to.path === '/cronjobs/cronjob/operate') {
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/cronjobs/cronjob');
} else if (to.meta.activeMenu === '/containers' && to.path === '/containers/container/operate') {
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/container');
} else {
localStorage.setItem('cachedRoute' + to.meta.activeMenu, to.path);
}
}
}
isRedirecting = false;
NProgress.done();

View file

@ -28,6 +28,16 @@ const cronRouter = {
requiresAuth: false,
},
},
{
path: 'cronjob/operate',
name: 'CronjobOperate',
component: () => import('@/views/cronjob/cronjob/operate/index.vue'),
hidden: true,
meta: {
activeMenu: '/cronjobs',
requiresAuth: false,
},
},
{
path: 'library',
name: 'Library',

View file

@ -444,6 +444,21 @@ export function getProvider(provider: string): string {
}
}
export function transferTimeToSecond(item: string): any {
if (item.indexOf('s') !== -1) {
return Number(item.replaceAll('s', ''));
}
if (item.indexOf('m') !== -1) {
return Number(item.replaceAll('m', '')) * 60;
}
if (item.indexOf('h') !== -1) {
return Number(item.replaceAll('h', '')) * 60 * 60;
}
if (item.indexOf('d') !== -1) {
return Number(item.replaceAll('d', '')) * 60 * 60 * 24;
}
return Number(item);
}
export function splitTime(item: string): any {
if (item.indexOf('s') !== -1) {
return { time: Number(item.replaceAll('s', '')), unit: 's' };

View file

@ -2,7 +2,7 @@
<div>
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
<template #leftToolBar>
<el-button type="primary" @click="onOpenDialog('create')">
<el-button type="primary" @click="onOpenDialog('')">
{{ $t('commons.button.create') }}{{ $t('menu.cronjob') }}
</el-button>
<el-button-group class="ml-4">
@ -150,14 +150,12 @@
</el-form>
</template>
</OpDialog>
<OperateDialog @search="search" ref="dialogRef" />
<Records @search="search" ref="dialogRecordRef" />
<Backups @search="search" ref="dialogBackupRef" />
</div>
</template>
<script lang="ts" setup>
import OperateDialog from '@/views/cronjob/cronjob/operate/index.vue';
import Records from '@/views/cronjob/cronjob/record/index.vue';
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
import { computed, onMounted, reactive, ref } from 'vue';
@ -168,6 +166,7 @@ import { ElMessageBox } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { transSpecToStr } from './helper';
import { GlobalStore } from '@/store';
import router from '@/routers';
const globalStore = GlobalStore();
const mobile = computed(() => {
@ -219,29 +218,8 @@ const search = async (column?: any) => {
const dialogRecordRef = ref();
const dialogBackupRef = ref();
const dialogRef = ref();
const onOpenDialog = async (
title: string,
rowData: Partial<Cronjob.CronjobInfo> = {
specObjs: [
{
specType: 'perMonth',
week: 1,
day: 3,
hour: 1,
minute: 30,
second: 30,
},
],
type: 'shell',
retainCopies: 7,
},
) => {
let params = {
title,
rowData: { ...rowData },
};
dialogRef.value!.acceptParams(params);
const onOpenDialog = async (id: string) => {
router.push({ name: 'CronjobOperate', query: { id: id } });
};
const onDelete = async (row: Cronjob.CronjobInfo | null) => {
@ -362,7 +340,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: (row: Cronjob.CronjobInfo) => {
onOpenDialog('edit', row);
onOpenDialog(row.id + '');
},
},
{

File diff suppressed because it is too large Load diff