mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-18 11:26:10 +08:00
parent
4f8fb26d89
commit
40f02889fe
22 changed files with 644 additions and 22 deletions
|
@ -1,6 +1,8 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
|
@ -53,6 +55,49 @@ func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
|
|||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
// @Summary Export cronjob list
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByIDs true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /cronjobs/export [post]
|
||||
func (b *BaseApi) ExportCronjob(c *gin.Context) {
|
||||
var req dto.OperateByIDs
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content, err := cronjobService.Export(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
http.ServeContent(c.Writer, c.Request, "", time.Now(), strings.NewReader(content))
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
// @Summary Import cronjob list
|
||||
// @Accept json
|
||||
// @Param request body dto.CronjobImport true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /cronjobs/import [post]
|
||||
func (b *BaseApi) ImportCronjob(c *gin.Context) {
|
||||
var req dto.CronjobImport
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Import(req.Cronjobs); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
// @Summary Load script options
|
||||
// @Success 200 {array} dto.ScriptOptions
|
||||
|
|
|
@ -28,6 +28,9 @@ type OperationWithName struct {
|
|||
type OperateByID struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
type OperateByIDs struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
|
||||
type Operate struct {
|
||||
Operation string `json:"operation" validate:"required"`
|
||||
|
|
|
@ -123,6 +123,53 @@ type CronjobInfo struct {
|
|||
AlertCount uint `json:"alertCount"`
|
||||
}
|
||||
|
||||
type CronjobImport struct {
|
||||
Cronjobs []CronjobTrans `json:"cronjobs"`
|
||||
}
|
||||
type CronjobTrans struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SpecCustom bool `json:"specCustom"`
|
||||
Spec string `json:"spec"`
|
||||
|
||||
Executor string `json:"executor"`
|
||||
ScriptMode string `json:"scriptMode"`
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
User string `json:"user"`
|
||||
URL string `json:"url"`
|
||||
|
||||
ScriptName string `json:"scriptName"`
|
||||
Apps []TransHelper `json:"apps"`
|
||||
Websites []string `json:"websites"`
|
||||
DBType string `json:"dbType"`
|
||||
DBNames []TransHelper `json:"dbName"`
|
||||
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
|
||||
IsDir bool `json:"isDir"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
|
||||
RetainCopies uint64 `json:"retainCopies"`
|
||||
RetryTimes uint `json:"retryTimes"`
|
||||
Timeout uint `json:"timeout"`
|
||||
IgnoreErr bool `json:"ignoreErr"`
|
||||
SnapshotRule string `json:"snapshotRule"`
|
||||
Secret string `json:"secret"`
|
||||
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
}
|
||||
type TransHelper struct {
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
}
|
||||
|
||||
type ScriptOptions struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
|
@ -36,6 +36,7 @@ type IAppInstallRepo interface {
|
|||
Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error)
|
||||
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
|
||||
LoadBaseInfo(key string, name string) (*RootInfo, error)
|
||||
LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error)
|
||||
GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error)
|
||||
}
|
||||
|
||||
|
@ -246,3 +247,23 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
|
|||
info.Status = appInstall.Status
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (a *AppInstallRepo) LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error) {
|
||||
var (
|
||||
app model.App
|
||||
appInstall model.AppInstall
|
||||
)
|
||||
if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(name) == 0 {
|
||||
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := global.DB.Where("app_id = ? AND name = ?", app.ID, name).First(&appInstall).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &appInstall, nil
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ type ICronjobService interface {
|
|||
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
|
||||
CleanRecord(req dto.CronjobClean) error
|
||||
|
||||
Export(req dto.OperateByIDs) (string, error)
|
||||
Import(req []dto.CronjobTrans) error
|
||||
LoadScriptOptions() []dto.ScriptOptions
|
||||
|
||||
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
|
||||
|
@ -102,6 +104,202 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
|||
return &item, err
|
||||
}
|
||||
|
||||
func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
cronjobs, err := cronjobRepo.List(repo.WithByIDs(req.IDs))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var data []dto.CronjobTrans
|
||||
for _, cronjob := range cronjobs {
|
||||
item := dto.CronjobTrans{
|
||||
Name: cronjob.Name,
|
||||
Type: cronjob.Type,
|
||||
SpecCustom: cronjob.SpecCustom,
|
||||
Spec: cronjob.Spec,
|
||||
Executor: cronjob.Executor,
|
||||
ScriptMode: cronjob.ScriptMode,
|
||||
Script: cronjob.Script,
|
||||
Command: cronjob.Command,
|
||||
ContainerName: cronjob.ContainerName,
|
||||
User: cronjob.User,
|
||||
URL: cronjob.URL,
|
||||
DBType: cronjob.DBType,
|
||||
ExclusionRules: cronjob.ExclusionRules,
|
||||
IsDir: cronjob.IsDir,
|
||||
SourceDir: cronjob.SourceDir,
|
||||
RetainCopies: cronjob.RetainCopies,
|
||||
RetryTimes: cronjob.RetryTimes,
|
||||
Timeout: cronjob.Timeout,
|
||||
IgnoreErr: cronjob.IgnoreErr,
|
||||
Secret: cronjob.Secret,
|
||||
SnapshotRule: cronjob.SnapshotRule,
|
||||
}
|
||||
switch cronjob.Type {
|
||||
case "app":
|
||||
if cronjob.AppID == "all" {
|
||||
break
|
||||
}
|
||||
apps := loadAppsForJob(cronjob)
|
||||
for _, app := range apps {
|
||||
item.Apps = append(item.Apps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name})
|
||||
}
|
||||
case "website":
|
||||
if cronjob.Website == "all" {
|
||||
break
|
||||
}
|
||||
websites := loadWebsForJob(cronjob)
|
||||
for _, website := range websites {
|
||||
item.Websites = append(item.Websites, website.Alias)
|
||||
}
|
||||
case "database":
|
||||
if cronjob.DBName == "all" {
|
||||
break
|
||||
}
|
||||
databases := loadDbsForJob(cronjob)
|
||||
for _, db := range databases {
|
||||
item.DBNames = append(item.DBNames, dto.TransHelper{Name: db.Database, DetailName: db.Name})
|
||||
}
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
itemJson, err := json.Marshal(&data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(itemJson), nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
continue
|
||||
}
|
||||
cronjob := model.Cronjob{
|
||||
Name: item.Name,
|
||||
Type: item.Type,
|
||||
SpecCustom: item.SpecCustom,
|
||||
Spec: item.Spec,
|
||||
Executor: item.Executor,
|
||||
ScriptMode: item.ScriptMode,
|
||||
Script: item.Script,
|
||||
Command: item.Command,
|
||||
ContainerName: item.ContainerName,
|
||||
User: item.User,
|
||||
URL: item.URL,
|
||||
DBType: item.DBType,
|
||||
ExclusionRules: item.ExclusionRules,
|
||||
IsDir: item.IsDir,
|
||||
SourceDir: item.SourceDir,
|
||||
RetainCopies: item.RetainCopies,
|
||||
RetryTimes: item.RetryTimes,
|
||||
Timeout: item.Timeout,
|
||||
IgnoreErr: item.IgnoreErr,
|
||||
Secret: item.Secret,
|
||||
SnapshotRule: item.SnapshotRule,
|
||||
}
|
||||
hasNotFound := false
|
||||
switch item.Type {
|
||||
case "app":
|
||||
if len(item.Apps) == 0 {
|
||||
cronjob.AppID = "all"
|
||||
break
|
||||
}
|
||||
var appIDs []string
|
||||
for _, app := range item.Apps {
|
||||
appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName)
|
||||
if err != nil {
|
||||
hasNotFound = true
|
||||
continue
|
||||
}
|
||||
appIDs = append(appIDs, fmt.Sprintf("%v", appItem.ID))
|
||||
}
|
||||
cronjob.AppID = strings.Join(appIDs, ",")
|
||||
case "website":
|
||||
if len(item.Websites) == 0 {
|
||||
cronjob.Website = "all"
|
||||
break
|
||||
}
|
||||
var webIDs []string
|
||||
for _, web := range item.Websites {
|
||||
webItem, err := websiteRepo.GetFirst(websiteRepo.WithAlias(web))
|
||||
if err != nil {
|
||||
hasNotFound = true
|
||||
continue
|
||||
}
|
||||
webIDs = append(webIDs, fmt.Sprintf("%v", webItem.ID))
|
||||
}
|
||||
cronjob.Website = strings.Join(webIDs, ",")
|
||||
case "database":
|
||||
if len(item.DBNames) == 0 {
|
||||
cronjob.DBName = "all"
|
||||
break
|
||||
}
|
||||
var dbIDs []string
|
||||
if cronjob.DBType == "postgresql" {
|
||||
for _, db := range item.DBNames {
|
||||
dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(db.Name), repo.WithByName(db.DetailName))
|
||||
if err != nil {
|
||||
hasNotFound = true
|
||||
continue
|
||||
}
|
||||
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
|
||||
}
|
||||
} else {
|
||||
for _, db := range item.DBNames {
|
||||
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(db.Name), repo.WithByName(db.DetailName))
|
||||
if err != nil {
|
||||
hasNotFound = true
|
||||
continue
|
||||
}
|
||||
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
|
||||
}
|
||||
}
|
||||
cronjob.DBName = strings.Join(dbIDs, ",")
|
||||
}
|
||||
var acIDs []string
|
||||
for _, ac := range item.SourceAccounts {
|
||||
backup, err := backupRepo.Get(repo.WithByName(ac))
|
||||
if err != nil {
|
||||
hasNotFound = true
|
||||
continue
|
||||
}
|
||||
if ac == item.DownloadAccount {
|
||||
cronjob.DownloadAccountID = backup.ID
|
||||
}
|
||||
acIDs = append(acIDs, fmt.Sprintf("%v", backup.ID))
|
||||
}
|
||||
cronjob.SourceAccountIDs = strings.Join(acIDs, ",")
|
||||
if hasNotFound {
|
||||
cronjob.Status = constant.StatusPending
|
||||
} else {
|
||||
cronjob.Status = constant.StatusDisable
|
||||
}
|
||||
if item.AlertCount != 0 {
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert)
|
||||
}
|
||||
_ = cronjobRepo.Create(&cronjob)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) LoadScriptOptions() []dto.ScriptOptions {
|
||||
scripts, err := scriptRepo.List()
|
||||
if err != nil {
|
||||
|
@ -411,6 +609,9 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
|||
}
|
||||
}
|
||||
|
||||
if cronModel.Status == constant.StatusPending {
|
||||
upMap["status"] = constant.StatusEnable
|
||||
}
|
||||
upMap["name"] = req.Name
|
||||
upMap["spec_custom"] = req.SpecCustom
|
||||
upMap["spec"] = spec
|
||||
|
|
|
@ -24,22 +24,7 @@ import (
|
|||
)
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
apps, _ = appInstallRepo.ListBy(context.Background())
|
||||
} else {
|
||||
appIds := strings.Split(cronjob.AppID, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(appIds); i++ {
|
||||
itemID, _ := strconv.Atoi(appIds[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
appItems, err := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps = appItems
|
||||
}
|
||||
apps := loadAppsForJob(cronjob)
|
||||
if len(apps) == 0 {
|
||||
return errors.New("no such app in database!")
|
||||
}
|
||||
|
@ -344,6 +329,23 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
|
|||
return nil
|
||||
}
|
||||
|
||||
func loadAppsForJob(cronjob model.Cronjob) []model.AppInstall {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
apps, _ = appInstallRepo.ListBy(context.Background())
|
||||
} else {
|
||||
appIds := strings.Split(cronjob.AppID, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(appIds); i++ {
|
||||
itemID, _ := strconv.Atoi(appIds[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
appItems, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
|
||||
apps = appItems
|
||||
}
|
||||
return apps
|
||||
}
|
||||
|
||||
type DatabaseHelper struct {
|
||||
ID uint
|
||||
DBType string
|
||||
|
|
|
@ -5,6 +5,7 @@ const (
|
|||
StatusCanceled = "Canceled"
|
||||
StatusDone = "Done"
|
||||
StatusWaiting = "Waiting"
|
||||
StatusPending = "Pending"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
StatusUploading = "Uploading"
|
||||
|
|
|
@ -13,6 +13,8 @@ func (s *CronjobRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
{
|
||||
cmdRouter.POST("", baseApi.CreateCronjob)
|
||||
cmdRouter.POST("/next", baseApi.LoadNextHandle)
|
||||
cmdRouter.POST("/import", baseApi.ImportCronjob)
|
||||
cmdRouter.POST("/export", baseApi.ExportCronjob)
|
||||
cmdRouter.POST("/load/info", baseApi.LoadCronjobInfo)
|
||||
cmdRouter.GET("/script/options", baseApi.LoadScriptOptions)
|
||||
cmdRouter.POST("/del", baseApi.DeleteCronjob)
|
||||
|
|
|
@ -101,6 +101,48 @@ export namespace Cronjob {
|
|||
alertTitle: string;
|
||||
alertMethod: string;
|
||||
}
|
||||
export interface CronjobTrans {
|
||||
name: string;
|
||||
type: string;
|
||||
specCustom: boolean;
|
||||
spec: string;
|
||||
group: string;
|
||||
|
||||
executor: string;
|
||||
scriptMode: string;
|
||||
script: string;
|
||||
command: string;
|
||||
containerName: string;
|
||||
user: string;
|
||||
url: string;
|
||||
|
||||
scriptName: string;
|
||||
apps: Array<TransHelper>;
|
||||
websites: Array<string>;
|
||||
dbType: string;
|
||||
dbNames: Array<TransHelper>;
|
||||
|
||||
exclusionRules: string;
|
||||
|
||||
isDir: boolean;
|
||||
sourceDir: string;
|
||||
|
||||
retainCopies: number;
|
||||
retryTimes: number;
|
||||
timeout: number;
|
||||
ignoreErr: boolean;
|
||||
snapshotRule: string;
|
||||
secret: string;
|
||||
|
||||
sourceAccounts: Array<string>;
|
||||
downloadAccount: string;
|
||||
|
||||
alertCount: number;
|
||||
}
|
||||
export interface TransHelper {
|
||||
name: string;
|
||||
detailName: string;
|
||||
}
|
||||
export interface snapshotRule {
|
||||
withImage: boolean;
|
||||
ignoreAppIDs: Array<Number>;
|
||||
|
|
|
@ -11,6 +11,13 @@ export const loadNextHandle = (spec: string) => {
|
|||
return http.post<Array<String>>(`/cronjobs/next`, { spec: spec });
|
||||
};
|
||||
|
||||
export const importCronjob = (trans: Array<Cronjob.CronjobTrans>) => {
|
||||
return http.post('cronjobs/import', { cronjobs: trans }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const exportCronjob = (params: { ids: Array<number> }) => {
|
||||
return http.download<BlobPart>('cronjobs/export', params, { responseType: 'blob', timeout: TimeoutEnum.T_40S });
|
||||
};
|
||||
|
||||
export const loadCronjobInfo = (id: number) => {
|
||||
return http.post<Cronjob.CronjobOperate>(`/cronjobs/load/info`, { id: id });
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ const message = {
|
|||
verify: 'Verify',
|
||||
saveAndEnable: 'Save and enable',
|
||||
import: 'Import',
|
||||
export: 'Export',
|
||||
power: 'Authorization',
|
||||
search: 'Search',
|
||||
refresh: 'Refresh',
|
||||
|
@ -298,6 +299,7 @@ const message = {
|
|||
normal: 'Normal',
|
||||
building: 'Building',
|
||||
upgrading: 'Upgrading',
|
||||
pending: 'Pending Edit',
|
||||
rebuilding: 'Rebuilding',
|
||||
deny: 'Denied',
|
||||
accept: 'Accepted',
|
||||
|
@ -988,6 +990,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Create cron job',
|
||||
edit: 'Edit cron job',
|
||||
errImport: 'File content exception:',
|
||||
importHelper:
|
||||
'Duplicate scheduled tasks will be automatically skipped during import. Tasks will be set to [Disabled] status by default, and set to [Pending Edit] status when data association is abnormal.',
|
||||
changeStatus: 'Change status',
|
||||
disableMsg: 'This will stop the scheduled task from automatically executing. Do you want to continue?',
|
||||
enableMsg: 'This will allow the scheduled task to automatically execute. Do you want to continue?',
|
||||
|
|
|
@ -48,6 +48,7 @@ const message = {
|
|||
verify: '確認する',
|
||||
saveAndEnable: '保存して有効にします',
|
||||
import: '輸入',
|
||||
export: 'エクスポート',
|
||||
power: '認可',
|
||||
search: '検索',
|
||||
refresh: 'リロード',
|
||||
|
@ -287,6 +288,7 @@ const message = {
|
|||
normal: '普通',
|
||||
building: '建物',
|
||||
upgrading: 'アップグレード',
|
||||
pending: '編集待ち',
|
||||
rebuilding: '再構築',
|
||||
deny: '拒否されました',
|
||||
accept: '受け入れられました',
|
||||
|
@ -958,6 +960,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Cronジョブを作成します',
|
||||
edit: 'Cronジョブを編集します',
|
||||
errImport: 'ファイル内容異常:',
|
||||
importHelper:
|
||||
'インポート時に同名のスケジュールタスクは自動的にスキップされます。タスクはデフォルトで【無効】状態に設定され、データ関連付け異常時には【編集待ち】状態に設定されます。',
|
||||
changeStatus: 'ステータスを変更します',
|
||||
disableMsg: 'これにより、スケジュールされたタスクが自動的に実行されなくなります。続けたいですか?',
|
||||
enableMsg: 'これにより、スケジュールされたタスクが自動的に実行されます。続けたいですか?',
|
||||
|
|
|
@ -48,6 +48,7 @@ const message = {
|
|||
verify: '검증',
|
||||
saveAndEnable: '저장 및 활성화',
|
||||
import: '가져오기',
|
||||
export: '내보내기',
|
||||
power: '권한 부여',
|
||||
search: '검색',
|
||||
refresh: '새로고침',
|
||||
|
@ -289,6 +290,7 @@ const message = {
|
|||
normal: '정상',
|
||||
building: '빌드 중',
|
||||
upgrading: '업그레이드 중',
|
||||
pending: '편집 대기',
|
||||
rebuilding: '재빌드 중',
|
||||
deny: '거부됨',
|
||||
accept: '수락됨',
|
||||
|
@ -948,6 +950,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: '크론 작업 생성',
|
||||
edit: '크론 작업 수정',
|
||||
errImport: '파일 내용 이상:',
|
||||
importHelper:
|
||||
'가져오기 시 동일한 이름의 예약 작업은 자동으로 건너뜁니다. 작업은 기본적으로 【비활성화】 상태로 설정되며, 데이터 연동 이상 시 【편집 대기】 상태로 설정됩니다.',
|
||||
changeStatus: '상태 변경',
|
||||
disableMsg: '이 작업은 예약된 작업이 자동으로 실행되지 않도록 멈춥니다. 계속하시겠습니까?',
|
||||
enableMsg: '이 작업은 예약된 작업이 자동으로 실행되도록 허용합니다. 계속하시겠습니까?',
|
||||
|
|
|
@ -48,6 +48,7 @@ const message = {
|
|||
verify: 'Sahkan',
|
||||
saveAndEnable: 'Simpan dan aktifkan',
|
||||
import: 'Import',
|
||||
export: 'Eksport',
|
||||
power: 'Pemberian Kuasa',
|
||||
search: 'Cari',
|
||||
refresh: 'Segarkan',
|
||||
|
@ -295,6 +296,7 @@ const message = {
|
|||
normal: 'Normal',
|
||||
building: 'Sedang Membina',
|
||||
upgrading: 'Sedang Meningkatkan',
|
||||
pending: 'Menunggu Edit',
|
||||
rebuilding: 'Sedang Membina Semula',
|
||||
deny: 'Ditolak',
|
||||
accept: 'Diterima',
|
||||
|
@ -979,6 +981,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Cipta tugas cron',
|
||||
edit: 'Edit tugas cron',
|
||||
errImport: 'Kandungan fail tidak normal:',
|
||||
importHelper:
|
||||
'Tugas terjadual dengan nama sama akan dilangkau secara automatik semasa import. Tugas akan ditetapkan ke status 【Lumpuh】 secara lalai, dan ditetapkan ke status 【Menunggu Edit】 apabila perkaitan data tidak normal.',
|
||||
changeStatus: 'Tukar status',
|
||||
disableMsg:
|
||||
'Ini akan menghentikan tugas berjadual daripada dilaksanakan secara automatik. Adakah anda mahu meneruskan?',
|
||||
|
|
|
@ -48,6 +48,7 @@ const message = {
|
|||
verify: 'Verificar',
|
||||
saveAndEnable: 'Salvar e ativar',
|
||||
import: 'Importar',
|
||||
export: 'Exportar',
|
||||
power: 'Autorização',
|
||||
search: 'Pesquisar',
|
||||
refresh: 'Atualizar',
|
||||
|
@ -293,6 +294,7 @@ const message = {
|
|||
normal: 'Normal',
|
||||
building: 'Construindo',
|
||||
upgrading: 'Atualizando',
|
||||
pending: 'Aguardando Edição',
|
||||
rebuilding: 'Reconstruindo',
|
||||
deny: 'Negado',
|
||||
accept: 'Aceito',
|
||||
|
@ -975,6 +977,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Criar tarefa cron',
|
||||
edit: 'Editar tarefa cron',
|
||||
errImport: 'Conteúdo do arquivo anormal:',
|
||||
importHelper:
|
||||
'Tarefas agendadas duplicadas serão automaticamente ignoradas durante a importação. As tarefas serão definidas como status 【Desativado】 por padrão, e como status 【Aguardando Edição】 quando a associação de dados for anormal.',
|
||||
changeStatus: 'Alterar status',
|
||||
disableMsg: 'Isso irá parar a execução automática da tarefa agendada. Você deseja continuar?',
|
||||
enableMsg: 'Isso permitirá que a tarefa agendada seja executada automaticamente. Você deseja continuar?',
|
||||
|
|
|
@ -48,6 +48,7 @@ const message = {
|
|||
verify: 'Проверить',
|
||||
saveAndEnable: 'Сохранить и включить',
|
||||
import: 'Импорт',
|
||||
export: 'Экспорт',
|
||||
power: 'Авторизация',
|
||||
search: 'Поиск',
|
||||
refresh: 'Обновить',
|
||||
|
@ -290,6 +291,7 @@ const message = {
|
|||
normal: 'Нормально',
|
||||
building: 'Сборка',
|
||||
upgrading: 'Обновление',
|
||||
pending: 'Ожидает редактирования',
|
||||
rebuilding: 'Пересборка',
|
||||
deny: 'Отказано',
|
||||
accept: 'Принято',
|
||||
|
@ -972,6 +974,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Создать задачу cron',
|
||||
edit: 'Редактировать задачу cron',
|
||||
errImport: 'Аномальное содержимое файла:',
|
||||
importHelper:
|
||||
'Повторяющиеся запланированные задачи будут автоматически пропущены при импорте. По умолчанию задачи устанавливаются в статус 【Отключено】, а при аномальной ассоциации данных - в статус 【Ожидает редактирования】.',
|
||||
changeStatus: 'Изменить статус',
|
||||
disableMsg: 'Это остановит автоматическое выполнение запланированной задачи. Хотите продолжить?',
|
||||
enableMsg: 'Это позволит запланированной задаче автоматически выполняться. Хотите продолжить?',
|
||||
|
|
|
@ -50,6 +50,7 @@ const message = {
|
|||
verify: 'Doğrula',
|
||||
saveAndEnable: 'Kaydet ve etkinleştir',
|
||||
import: 'İçe Aktar',
|
||||
export: 'Dışa Aktar',
|
||||
power: 'Yetkilendirme',
|
||||
search: 'Ara',
|
||||
refresh: 'Yenile',
|
||||
|
@ -302,6 +303,7 @@ const message = {
|
|||
normal: 'Normal',
|
||||
building: 'İnşa Ediliyor',
|
||||
upgrading: 'Yükseltiliyor',
|
||||
pending: 'Düzenleme Bekliyor',
|
||||
rebuilding: 'Yeniden İnşa Ediliyor',
|
||||
deny: 'Reddedildi',
|
||||
accept: 'Kabul Edildi',
|
||||
|
@ -1000,6 +1002,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: 'Cron görevi oluştur',
|
||||
edit: 'Cron görevini düzenle',
|
||||
errImport: 'Dosya içeriği anormal:',
|
||||
importHelper:
|
||||
'İçe aktarım sırasında aynı isimli zamanlanmış görevler otomatik olarak atlanacaktır. Görevler varsayılan olarak 【Devre Dışı】 durumuna ayarlanır ve veri ilişkilendirme anormalse 【Düzenleme Bekliyor】 durumuna ayarlanır.',
|
||||
changeStatus: 'Durumu değiştir',
|
||||
disableMsg: 'Bu, zamanlanmış görevin otomatik olarak yürütülmesini durduracaktır. Devam etmek istiyor musunuz?',
|
||||
enableMsg:
|
||||
|
|
|
@ -51,6 +51,7 @@ const message = {
|
|||
saveAndEnable: '保存並啟用',
|
||||
import: '導入',
|
||||
power: '授權',
|
||||
export: '導出',
|
||||
search: '搜索',
|
||||
refresh: '刷新',
|
||||
get: '獲取',
|
||||
|
@ -289,6 +290,7 @@ const message = {
|
|||
normal: '正常',
|
||||
building: '製作鏡像中',
|
||||
upgrading: '升級中',
|
||||
pending: '待編輯',
|
||||
rebuilding: '重建中',
|
||||
deny: '已屏蔽',
|
||||
accept: '已放行',
|
||||
|
@ -942,6 +944,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: '創建計劃任務',
|
||||
edit: '編輯計劃任務',
|
||||
errImport: '文件內容異常:',
|
||||
importHelper:
|
||||
'導入時將自動跳過重名計劃任務。任務默認設置為【停用】狀態,數據關聯異常時,設置為【待編輯】狀態。',
|
||||
changeStatus: '狀態修改',
|
||||
disableMsg: '停止計劃任務會導致該任務不再自動執行。是否繼續?',
|
||||
enableMsg: '啟用計劃任務會讓該任務定期自動執行。是否繼續?',
|
||||
|
|
|
@ -50,6 +50,7 @@ const message = {
|
|||
verify: '验证',
|
||||
saveAndEnable: '保存并启用',
|
||||
import: '导入',
|
||||
export: '导出',
|
||||
power: '授权',
|
||||
search: '搜索',
|
||||
refresh: '刷新',
|
||||
|
@ -287,6 +288,7 @@ const message = {
|
|||
normal: '正常',
|
||||
building: '制作镜像中',
|
||||
upgrading: '升级中',
|
||||
pending: '待编辑',
|
||||
rebuilding: '重建中',
|
||||
deny: '已屏蔽',
|
||||
accept: '已放行',
|
||||
|
@ -940,6 +942,9 @@ const message = {
|
|||
cronjob: {
|
||||
create: '创建计划任务',
|
||||
edit: '编辑计划任务',
|
||||
errImport: '文件内容异常:',
|
||||
importHelper:
|
||||
'导入时将自动跳过重名计划任务。任务默认设置为【停用】状态,数据关联异常时,设置为【待编辑】状态。',
|
||||
changeStatus: '状态修改',
|
||||
disableMsg: '停止计划任务会导致该任务不再自动执行。是否继续?',
|
||||
enableMsg: '启用计划任务会让该任务定期自动执行。是否继续?',
|
||||
|
|
149
frontend/src/views/cronjob/cronjob/import/index.vue
Normal file
149
frontend/src/views/cronjob/cronjob/import/index.vue
Normal file
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<DialogPro v-model="visible" :title="$t('commons.button.import')" size="large">
|
||||
<div>
|
||||
<el-alert :closable="false" show-icon type="info" :title="$t('cronjob.importHelper')" />
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
ref="uploadRef"
|
||||
class="upload mt-2"
|
||||
:show-file-list="false"
|
||||
:limit="1"
|
||||
:on-change="fileOnChange"
|
||||
:on-exceed="handleExceed"
|
||||
v-model:file-list="uploaderFiles"
|
||||
>
|
||||
<el-button class="float-left" type="primary">{{ $t('commons.button.upload') }}</el-button>
|
||||
</el-upload>
|
||||
|
||||
<el-button :disabled="selects.length === 0" @click="onImport" class="ml-2 mt-2">
|
||||
{{ $t('commons.button.import') }}
|
||||
</el-button>
|
||||
|
||||
<el-card class="mt-2 w-full" v-loading="loading">
|
||||
<el-table :data="data" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('cronjob.taskName')"
|
||||
:min-width="120"
|
||||
prop="name"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.type')"
|
||||
:min-width="120"
|
||||
prop="type"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ $t('cronjob.' + row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('cronjob.cronSpec')" show-overflow-tooltip :min-width="120">
|
||||
<template #default="{ row }">
|
||||
<div v-for="(item, index) of row.spec.split(',')" :key="index">
|
||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||
<span>
|
||||
{{ row.specCustom ? item : transSpecToStr(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.expand && row.spec.split(',').length > 3">
|
||||
<el-button type="primary" link @click="row.expand = true">
|
||||
{{ $t('commons.button.expand') }}...
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="row.expand && row.spec.split(',').length > 3">
|
||||
<el-button type="primary" link @click="row.expand = false">
|
||||
{{ $t('commons.button.collapse') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('cronjob.retainCopies')" :min-width="120" prop="retainCopies" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { transSpecToStr } from './../helper';
|
||||
import { genFileId, UploadFile, UploadFiles, UploadProps, UploadRawFile } from 'element-plus';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { importCronjob } from '@/api/modules/cronjob';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
const data = ref();
|
||||
|
||||
const uploadRef = ref();
|
||||
const uploaderFiles = ref();
|
||||
|
||||
const acceptParams = (): void => {
|
||||
visible.value = true;
|
||||
data.value = [];
|
||||
};
|
||||
|
||||
const handleSelectionChange = (val: any) => {
|
||||
selects.value = val;
|
||||
};
|
||||
|
||||
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
loading.value = true;
|
||||
data.value = [];
|
||||
uploaderFiles.value = uploadFiles;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const content = e.target.result as string;
|
||||
data.value = JSON.parse(content) as Cronjob.CronjobTrans;
|
||||
console.log(data.value);
|
||||
loading.value = false;
|
||||
} catch (error) {
|
||||
MsgError(i18n.global.t('cronjob.errImport') + error.message);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
reader.readAsText(_uploadFile.raw);
|
||||
};
|
||||
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
uploadRef.value!.clearFiles();
|
||||
const file = files[0] as UploadRawFile;
|
||||
file.uid = genFileId();
|
||||
uploadRef.value!.handleStart(file);
|
||||
};
|
||||
|
||||
const onImport = async () => {
|
||||
await importCronjob(selects.value).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
visible.value = false;
|
||||
emit('search');
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.upload {
|
||||
width: 60px;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
|
@ -16,6 +16,15 @@
|
|||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
|
||||
<el-button-group class="ml-4">
|
||||
<el-button @click="onImport">
|
||||
{{ $t('commons.button.import') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onExport">
|
||||
{{ $t('commons.button.export') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
<template #rightToolBar>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
|
@ -54,11 +63,12 @@
|
|||
:operate="true"
|
||||
/>
|
||||
<Status
|
||||
v-else
|
||||
v-if="row.status === 'Disable'"
|
||||
@click="onChangeStatus(row.id, 'enable')"
|
||||
:status="row.status"
|
||||
:operate="true"
|
||||
/>
|
||||
<Status v-if="row.status === 'Pending'" :status="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('cronjob.cronSpec')" show-overflow-tooltip :min-width="120">
|
||||
|
@ -166,7 +176,9 @@
|
|||
</el-form>
|
||||
</template>
|
||||
</OpDialog>
|
||||
<OpDialog ref="opExportRef" @search="search" @submit="onSubmitExport()" />
|
||||
<Records @search="search" ref="dialogRecordRef" />
|
||||
<Import @search="search" ref="dialogImportRef" />
|
||||
<Backups @search="search" ref="dialogBackupRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -174,8 +186,9 @@
|
|||
<script lang="ts" setup>
|
||||
import Records from '@/views/cronjob/cronjob/record/index.vue';
|
||||
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
|
||||
import Import from '@/views/cronjob/cronjob/import/index.vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCronjob, getCronjobPage, handleOnce, updateStatus } from '@/api/modules/cronjob';
|
||||
import { deleteCronjob, exportCronjob, getCronjobPage, handleOnce, updateStatus } from '@/api/modules/cronjob';
|
||||
import i18n from '@/lang';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
@ -183,6 +196,7 @@ import { MsgSuccess } from '@/utils/message';
|
|||
import { hasBackup, transSpecToStr } from './helper';
|
||||
import { GlobalStore } from '@/store';
|
||||
import router from '@/routers';
|
||||
import { getCurrentDateFormatted } from '@/utils/util';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const mobile = computed(() => {
|
||||
|
@ -198,6 +212,8 @@ const opRef = ref();
|
|||
const showClean = ref();
|
||||
const cleanData = ref();
|
||||
const cleanRemoteData = ref();
|
||||
const opExportRef = ref();
|
||||
const dialogImportRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
|
@ -286,6 +302,47 @@ const onSubmitDelete = async () => {
|
|||
});
|
||||
};
|
||||
|
||||
const onImport = () => {
|
||||
dialogImportRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const onExport = async () => {
|
||||
let names = [];
|
||||
let ids = [];
|
||||
for (const item of selects.value) {
|
||||
names.push(item.name);
|
||||
ids.push(item.id);
|
||||
}
|
||||
operateIDs.value = ids;
|
||||
opExportRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.export'),
|
||||
names: names,
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('menu.cronjob'),
|
||||
i18n.global.t('commons.button.export'),
|
||||
]),
|
||||
api: null,
|
||||
params: null,
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitExport = async () => {
|
||||
loading.value = true;
|
||||
await exportCronjob({ ids: operateIDs.value })
|
||||
.then((res) => {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = '1panel-cronjob-' + getCurrentDateFormatted() + '.json';
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeStatus = async (id: number, status: string) => {
|
||||
ElMessageBox.confirm(i18n.global.t('cronjob.' + status + 'Msg'), i18n.global.t('cronjob.changeStatus'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
|
|
|
@ -866,14 +866,14 @@ const search = async () => {
|
|||
|
||||
form.scriptID = res.data.scriptID;
|
||||
form.appID = res.data.appID;
|
||||
form.appIdList = res.data.appID.split(',') || [];
|
||||
form.appIdList = res.data.appID ? res.data.appID.split(',') : [];
|
||||
form.website = res.data.website;
|
||||
form.websiteList = res.data.website.split(',') || [];
|
||||
form.websiteList = res.data.website ? res.data.website.split(',') : [];
|
||||
form.exclusionRules = res.data.exclusionRules;
|
||||
form.ignoreFiles = res.data.exclusionRules.split(',');
|
||||
form.ignoreFiles = res.data.exclusionRules ? res.data.exclusionRules.split(',') : [];
|
||||
form.dbType = res.data.dbType;
|
||||
form.dbName = res.data.dbName;
|
||||
form.dbNameList = res.data.dbName.split(',') || [];
|
||||
form.dbNameList = res.data.dbName ? res.data.dbName.split(',') : [];
|
||||
form.url = res.data.url;
|
||||
form.withImage = res.data.snapshotRule.withImage;
|
||||
form.ignoreAppIDs = res.data.snapshotRule.ignoreAppIDs;
|
||||
|
|
Loading…
Add table
Reference in a new issue