feat: Support the selection of scripts from the script library for cr… (#8552)

This commit is contained in:
ssongliu 2025-05-06 18:09:37 +08:00 committed by GitHub
parent 91843382c3
commit 64a14a6b2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 407 additions and 191 deletions

View file

@ -53,6 +53,16 @@ func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Tags Cronjob
// @Summary Load script options
// @Success 200 {array} dto.ScriptOptions
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/script/options [get]
func (b *BaseApi) LoadScriptOptions(c *gin.Context) {
helper.SuccessWithData(c, cronjobService.LoadScriptOptions())
}
// @Tags Cronjob // @Tags Cronjob
// @Summary Load cronjob spec time // @Summary Load cronjob spec time
// @Accept json // @Accept json

View file

@ -29,6 +29,7 @@ type CronjobOperate struct {
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
User string `json:"user"` User string `json:"user"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"` AppID string `json:"appID"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
@ -84,6 +85,7 @@ type CronjobInfo struct {
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
User string `json:"user"` User string `json:"user"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"` AppID string `json:"appID"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
@ -109,6 +111,11 @@ type CronjobInfo struct {
AlertCount uint `json:"alertCount"` AlertCount uint `json:"alertCount"`
} }
type ScriptOptions struct {
ID uint `json:"id"`
Name string `json:"name"`
}
type SearchRecord struct { type SearchRecord struct {
PageInfo PageInfo
CronjobID int `json:"cronjobID"` CronjobID int `json:"cronjobID"`

View file

@ -21,6 +21,7 @@ type Cronjob struct {
Script string `json:"script"` Script string `json:"script"`
User string `json:"user"` User string `json:"user"`
ScriptID uint `json:"scriptID"`
Website string `json:"website"` Website string `json:"website"`
AppID string `json:"appID"` AppID string `json:"appID"`
DBType string `json:"dbType"` DBType string `json:"dbType"`
@ -55,3 +56,9 @@ type JobRecords struct {
Status string `json:"status"` Status string `json:"status"`
Message string `json:"message"` Message string `json:"message"`
} }
type ScriptLibrary struct {
BaseModel
Name string `json:"name" gorm:"not null;"`
Script string `json:"script" gorm:"not null;"`
}

76
agent/app/repo/script.go Normal file
View file

@ -0,0 +1,76 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
)
type ScriptRepo struct{}
type IScriptRepo interface {
Get(opts ...DBOption) (model.ScriptLibrary, error)
List(opts ...DBOption) ([]model.ScriptLibrary, error)
SyncAll(data []model.ScriptLibrary) error
}
func NewIScriptRepo() IScriptRepo {
return &ScriptRepo{}
}
func (u *ScriptRepo) Get(opts ...DBOption) (model.ScriptLibrary, error) {
var script model.ScriptLibrary
db := global.DB
if global.IsMaster {
db = global.CoreDB
}
for _, opt := range opts {
db = opt(db)
}
err := db.First(&script).Error
return script, err
}
func (u *ScriptRepo) List(opts ...DBOption) ([]model.ScriptLibrary, error) {
var ops []model.ScriptLibrary
itemDB := global.DB
if global.IsMaster {
itemDB = global.CoreDB
}
db := itemDB.Model(&model.ScriptLibrary{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&ops).Error
return ops, err
}
func (u *ScriptRepo) SyncAll(data []model.ScriptLibrary) error {
tx := global.DB.Begin()
var oldScripts []model.ScriptLibrary
_ = tx.Where("1 = 1").Find(&oldScripts).Error
oldScriptMap := make(map[string]uint)
for _, item := range oldScripts {
oldScriptMap[item.Name] = item.ID
}
for _, item := range data {
if val, ok := oldScriptMap[item.Name]; ok {
item.ID = val
delete(oldScriptMap, item.Name)
} else {
item.ID = 0
}
if err := tx.Model(model.ScriptLibrary{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
tx.Rollback()
return err
}
}
for _, val := range oldScriptMap {
if err := tx.Where("id = ?", val).Delete(&model.ScriptLibrary{}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return nil
}

View file

@ -2,6 +2,7 @@ package service
import ( import (
"bufio" "bufio"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path" "path"
@ -37,6 +38,8 @@ type ICronjobService interface {
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error) StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error CleanRecord(req dto.CronjobClean) error
LoadScriptOptions() []dto.ScriptOptions
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
LoadRecordLog(req dto.OperateByID) string LoadRecordLog(req dto.OperateByID) string
} }
@ -95,6 +98,27 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
return &item, err return &item, err
} }
func (u *CronjobService) LoadScriptOptions() []dto.ScriptOptions {
scripts, err := scriptRepo.List()
if err != nil {
return nil
}
var options []dto.ScriptOptions
for _, script := range scripts {
var item dto.ScriptOptions
item.ID = script.ID
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(script.Name), &translations)
if name, ok := translations["en"]; ok {
item.Name = strings.ReplaceAll(name, " ", "_")
} else {
item.Name = strings.ReplaceAll(script.Name, " ", "_")
}
options = append(options, item)
}
return options
}
func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interface{}, error) { func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interface{}, error) {
total, records, err := cronjobRepo.PageRecords( total, records, err := cronjobRepo.PageRecords(
search.Page, search.Page,
@ -362,6 +386,8 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
upMap["container_name"] = req.ContainerName upMap["container_name"] = req.ContainerName
upMap["executor"] = req.Executor upMap["executor"] = req.Executor
upMap["user"] = req.User upMap["user"] = req.User
upMap["script_id"] = req.ScriptID
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

@ -79,6 +79,14 @@ func (u *CronjobService) handleJob(cronjob *model.Cronjob, record model.JobRecor
) )
switch cronjob.Type { switch cronjob.Type {
case "shell": case "shell":
if cronjob.ScriptMode == "library" {
scriptItem, _ := scriptRepo.Get(repo.WithByID(cronjob.ScriptID))
if scriptItem.ID == 0 {
return nil, fmt.Errorf("load script from db failed, err: %v", err)
}
cronjob.Script = scriptItem.Script
cronjob.ScriptMode = "input"
}
if len(cronjob.Script) == 0 { if len(cronjob.Script) == 0 {
return nil, fmt.Errorf("the script content is empty and is skipped") return nil, fmt.Errorf("the script content is empty and is skipped")
} }

View file

@ -22,6 +22,7 @@ var (
imageRepoRepo = repo.NewIImageRepoRepo() imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo() composeRepo = repo.NewIComposeTemplateRepo()
scriptRepo = repo.NewIScriptRepo()
cronjobRepo = repo.NewICronjobRepo() cronjobRepo = repo.NewICronjobRepo()
hostRepo = repo.NewIHostRepo() hostRepo = repo.NewIHostRepo()

View file

@ -19,7 +19,7 @@ import (
) )
var AddTable = &gormigrate.Migration{ var AddTable = &gormigrate.Migration{
ID: "20250413-add-table", ID: "20250507-add-table",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate( return tx.AutoMigrate(
&model.AppDetail{}, &model.AppDetail{},
@ -43,6 +43,7 @@ var AddTable = &gormigrate.Migration{
&model.Firewall{}, &model.Firewall{},
&model.Ftp{}, &model.Ftp{},
&model.ImageRepo{}, &model.ImageRepo{},
&model.ScriptLibrary{},
&model.JobRecords{}, &model.JobRecords{},
&model.MonitorBase{}, &model.MonitorBase{},
&model.MonitorIO{}, &model.MonitorIO{},

View file

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

View file

@ -3,21 +3,23 @@ package dto
import "time" import "time"
type ScriptInfo struct { type ScriptInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Lable string `json:"lable"` IsInteractive bool `json:"isInteractive"`
Script string `json:"script"` Lable string `json:"lable"`
GroupList []uint `json:"groupList"` Script string `json:"script"`
GroupBelong []string `json:"groupBelong"` GroupList []uint `json:"groupList"`
IsSystem bool `json:"isSystem"` GroupBelong []string `json:"groupBelong"`
Description string `json:"description"` IsSystem bool `json:"isSystem"`
CreatedAt time.Time `json:"createdAt"` Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
} }
type ScriptOperate struct { type ScriptOperate struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` IsInteractive bool `json:"isInteractive"`
Script string `json:"script"` Name string `json:"name"`
Groups string `json:"groups"` Script string `json:"script"`
Description string `json:"description"` Groups string `json:"groups"`
Description string `json:"description"`
} }

View file

@ -2,9 +2,10 @@ package model
type ScriptLibrary struct { type ScriptLibrary struct {
BaseModel BaseModel
Name string `json:"name" gorm:"not null;"` Name string `json:"name" gorm:"not null;"`
Script string `json:"script" gorm:"not null;"` IsInteractive bool `json:"isInteractive"`
Groups string `json:"groups"` Script string `json:"script" gorm:"not null;"`
IsSystem bool `json:"isSystem"` Groups string `json:"groups"`
Description string `json:"description"` IsSystem bool `json:"isSystem"`
Description string `json:"description"`
} }

View file

@ -20,6 +20,7 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/common" "github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/files" "github.com/1Panel-dev/1Panel/core/utils/files"
"github.com/1Panel-dev/1Panel/core/utils/req_helper" "github.com/1Panel-dev/1Panel/core/utils/req_helper"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -57,7 +58,7 @@ func (u *ScriptService) Search(ctx *gin.Context, req dto.SearchPageWithGroup) (i
for _, itemData := range list { for _, itemData := range list {
var item dto.ScriptInfo var item dto.ScriptInfo
if err := copier.Copy(&item, &itemData); err != nil { if err := copier.Copy(&item, &itemData); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) global.LOG.Errorf("copy scripts to dto backup info failed, err: %v", err)
} }
if item.IsSystem { if item.IsSystem {
lang := strings.ToLower(common.GetLang(ctx)) lang := strings.ToLower(common.GetLang(ctx))
@ -117,6 +118,14 @@ func (u *ScriptService) Create(req dto.ScriptOperate) error {
if err := scriptRepo.Create(&itemData); err != nil { if err := scriptRepo.Create(&itemData); err != nil {
return err return err
} }
go func() {
if req.IsInteractive {
return
}
if err := xpack.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
}()
return nil return nil
} }
@ -130,6 +139,11 @@ func (u *ScriptService) Delete(req dto.OperateByIDs) error {
return err return err
} }
} }
go func() {
if err := xpack.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
}()
return nil return nil
} }
@ -142,10 +156,16 @@ func (u *ScriptService) Update(req dto.ScriptOperate) error {
updateMap["name"] = req.Name updateMap["name"] = req.Name
updateMap["script"] = req.Script updateMap["script"] = req.Script
updateMap["groups"] = req.Groups updateMap["groups"] = req.Groups
updateMap["is_interactive"] = req.IsInteractive
updateMap["description"] = req.Description updateMap["description"] = req.Description
if err := scriptRepo.Update(req.ID, updateMap); err != nil { if err := scriptRepo.Update(req.ID, updateMap); err != nil {
return err return err
} }
go func() {
if err := xpack.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
}()
return nil return nil
} }
@ -179,6 +199,7 @@ func (u *ScriptService) Sync() error {
if err != nil { if err != nil {
return fmt.Errorf("load scripts data.yaml from remote failed, err: %v", err) return fmt.Errorf("load scripts data.yaml from remote failed, err: %v", err)
} }
fmt.Println(string(dataRes))
syncTask.Log("download successful!") syncTask.Log("download successful!")
var scripts Scripts var scripts Scripts
@ -205,10 +226,11 @@ func (u *ScriptService) Sync() error {
itemDescription, _ := json.Marshal(item.Description) itemDescription, _ := json.Marshal(item.Description)
shell, _ := os.ReadFile(fmt.Sprintf("%s/scripts/sh/%s.sh", tmpDir, item.Key)) shell, _ := os.ReadFile(fmt.Sprintf("%s/scripts/sh/%s.sh", tmpDir, item.Key))
scriptItem := model.ScriptLibrary{ scriptItem := model.ScriptLibrary{
Name: string(itemName), Name: string(itemName),
IsSystem: true, IsInteractive: item.Interactive,
Script: string(shell), IsSystem: true,
Description: string(itemDescription), Script: string(shell),
Description: string(itemDescription),
} }
scriptsForDB = append(scriptsForDB, scriptItem) scriptsForDB = append(scriptsForDB, scriptItem)
} }
@ -243,5 +265,6 @@ type ScriptHelper struct {
Sort uint `json:"sort"` Sort uint `json:"sort"`
Groups string `json:"groups"` Groups string `json:"groups"`
Name map[string]string `json:"name"` Name map[string]string `json:"name"`
Interactive bool `json:"interactive"`
Description map[string]string `json:"description"` Description map[string]string `json:"description"`
} }

View file

@ -141,6 +141,12 @@ func (u *SettingService) Update(key, value string) error {
} }
case "UserName", "Password": case "UserName", "Password":
_ = global.SESSION.Clean() _ = global.SESSION.Clean()
case "Language":
go func() {
if err := xpack.Sync(constant.SyncLanguage); err != nil {
global.LOG.Errorf("sync language to node failed, err: %v", err)
}
}()
} }
return nil return nil

View file

@ -44,9 +44,11 @@ const (
const ( const (
SyncSystemProxy = "SyncSystemProxy" SyncSystemProxy = "SyncSystemProxy"
SyncScripts = "SyncScripts"
SyncBackupAccounts = "SyncBackupAccounts" SyncBackupAccounts = "SyncBackupAccounts"
SyncAlertSetting = "SyncAlertSetting" SyncAlertSetting = "SyncAlertSetting"
SyncCustomApp = "SyncCustomApp" SyncCustomApp = "SyncCustomApp"
SyncLanguage = "SyncLanguage"
) )
var WebUrlMap = map[string]struct{}{ var WebUrlMap = map[string]struct{}{

View file

@ -176,7 +176,3 @@ func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string {
return content return content
} }
} }
func GetLanguageFromDB() {
}

View file

@ -16,7 +16,7 @@ import (
) )
var AddTable = &gormigrate.Migration{ var AddTable = &gormigrate.Migration{
ID: "20240109-add-table", ID: "20240506-add-table",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate( return tx.AutoMigrate(
&model.OperationLog{}, &model.OperationLog{},
@ -27,6 +27,7 @@ var AddTable = &gormigrate.Migration{
&model.Host{}, &model.Host{},
&model.Command{}, &model.Command{},
&model.UpgradeLog{}, &model.UpgradeLog{},
&model.ScriptLibrary{},
) )
}, },
} }

View file

@ -105,6 +105,11 @@ export namespace Cronjob {
recordID: number; recordID: number;
backupAccountID: number; backupAccountID: number;
} }
export interface ScriptOptions {
id: number;
name: string;
script: string;
}
export interface SearchRecord extends ReqPage { export interface SearchRecord extends ReqPage {
cronjobID: number; cronjobID: number;
startTime: Date; startTime: Date;

View file

@ -15,6 +15,10 @@ export const loadCronjobInfo = (id: number) => {
return http.post<Cronjob.CronjobOperate>(`/cronjobs/load/info`, { id: id }); return http.post<Cronjob.CronjobOperate>(`/cronjobs/load/info`, { id: id });
}; };
export const loadScriptOptions = () => {
return http.get<Cronjob.ScriptOptions>(`/cronjobs/script/options`);
};
export const getRecordLog = (id: number) => { export const getRecordLog = (id: number) => {
return http.post<string>(`/cronjobs/records/log`, { id: id }); return http.post<string>(`/cronjobs/records/log`, { id: id });
}; };

View file

@ -0,0 +1,16 @@
<template>
<el-col :xs="24" :sm="span" :md="span" :lg="span" :xl="span">
<slot />
</el-col>
</template>
<script>
export default {
props: {
span: {
type: Number,
default: 10,
},
},
};
</script>

View file

@ -1069,6 +1069,9 @@ const message = {
alertTitle: 'Planned Task - {0} {1} Task Failure Alert', alertTitle: 'Planned Task - {0} {1} Task Failure Alert',
library: { library: {
script: 'Script', script: 'Script',
isInteractive: 'Interactive',
interactive: 'Interactive script',
interactiveHelper: 'Requires user input during execution and cannot be used in scheduled tasks.',
library: 'Script Library', library: 'Script Library',
create: 'Add Script', create: 'Add Script',
edit: 'Edit Script', edit: 'Edit Script',

View file

@ -1029,6 +1029,9 @@ const message = {
alertTitle: '計画タスク - {0}{1}タスク障害アラート', alertTitle: '計画タスク - {0}{1}タスク障害アラート',
library: { library: {
script: 'スクリプト', script: 'スクリプト',
isInteractive: '対話型',
interactive: '対話型スクリプト',
interactiveHelper: '実行中にユーザー入力が必要でスケジュールタスクでは使用できません',
library: 'スクリプトライブラリ', library: 'スクリプトライブラリ',
create: 'スクリプトを追加', create: 'スクリプトを追加',
edit: 'スクリプトを編集', edit: 'スクリプトを編集',

View file

@ -1023,6 +1023,9 @@ const message = {
alertTitle: '예정된 작업 - {0} {1} 작업 실패 경고', alertTitle: '예정된 작업 - {0} {1} 작업 실패 경고',
library: { library: {
script: '스크립트', script: '스크립트',
isInteractive: '대화형',
interactive: '대화형 스크립트',
interactiveHelper: '실행 사용자 입력이 필요하며 예약 작업에서는 사용할 없습니다.',
library: '스크립트 라이브러리', library: '스크립트 라이브러리',
create: '스크립트 추가', create: '스크립트 추가',
edit: '스크립트 수정', edit: '스크립트 수정',

View file

@ -1059,6 +1059,10 @@ const message = {
alertTitle: 'Tugas Terancang - {0} {1} Amaran Kegagalan Tugas', alertTitle: 'Tugas Terancang - {0} {1} Amaran Kegagalan Tugas',
library: { library: {
script: 'Skrip', script: 'Skrip',
isInteractive: 'Interaktif',
interactive: 'Skrip interaktif',
interactiveHelper:
'Memerlukan input pengguna semasa pelaksanaan dan tidak boleh digunakan dalam tugas terjadual.',
library: 'Perpustakaan Skrip', library: 'Perpustakaan Skrip',
create: 'Tambah Skrip', create: 'Tambah Skrip',
edit: 'Sunting Skrip', edit: 'Sunting Skrip',

View file

@ -1048,6 +1048,10 @@ const message = {
alertTitle: 'Tarefa Planejada - {0} {1} Alerta de Falha na Tarefa', alertTitle: 'Tarefa Planejada - {0} {1} Alerta de Falha na Tarefa',
library: { library: {
script: 'Script', script: 'Script',
isInteractive: 'Interativo',
interactive: 'Script interativo',
interactiveHelper:
'Requer entrada do usuário durante a execução e não pode ser usado em tarefas agendadas.',
library: 'Biblioteca de Scripts', library: 'Biblioteca de Scripts',
create: 'Adicionar Script', create: 'Adicionar Script',
edit: 'Editar Script', edit: 'Editar Script',

View file

@ -1053,6 +1053,10 @@ const message = {
alertTitle: 'Плановая задача - {0} «{1}» Оповещение о сбое задачи', alertTitle: 'Плановая задача - {0} «{1}» Оповещение о сбое задачи',
library: { library: {
script: 'Скрипт', script: 'Скрипт',
isInteractive: 'Интерактивный',
interactive: 'Интерактивный скрипт',
interactiveHelper:
'Требует ввода пользователя во время выполнения и не может использоваться в запланированных задачах.',
library: 'Библиотека скриптов', library: 'Библиотека скриптов',
create: 'Добавить скрипт', create: 'Добавить скрипт',
edit: 'Редактировать скрипт', edit: 'Редактировать скрипт',

View file

@ -1017,6 +1017,9 @@ const message = {
alertTitle: '計畫任務-{0}{1}任務失敗告警', alertTitle: '計畫任務-{0}{1}任務失敗告警',
library: { library: {
script: '腳本', script: '腳本',
isInteractive: '交互式',
interactive: '交互式腳本',
interactiveHelper: '在腳本執行過程中需要用戶輸入參數或做出選擇且無法用於計劃任務中',
library: '腳本庫', library: '腳本庫',
create: '添加腳本', create: '添加腳本',
edit: '修改腳本', edit: '修改腳本',

View file

@ -1015,6 +1015,9 @@ const message = {
alertTitle: '计划任务-{0} {1} 任务失败告警', alertTitle: '计划任务-{0} {1} 任务失败告警',
library: { library: {
script: '脚本', script: '脚本',
isInteractive: '交互式',
interactive: '交互式脚本',
interactiveHelper: '在脚本执行过程中需要用户输入参数或做出选择且无法用于计划任务中',
library: '脚本库', library: '脚本库',
create: '添加脚本', create: '添加脚本',
edit: '修改脚本', edit: '修改脚本',

View file

@ -2,70 +2,25 @@
<div v-loading="loading"> <div v-loading="loading">
<docker-status v-model:isActive="isActive" v-model:isExist="isExist" @search="search" /> <docker-status v-model:isActive="isActive" v-model:isExist="isExist" @search="search" />
<div class="card-interval" v-if="isExist && isActive">
<el-tag @click="searchWithStatus('all')" v-if="countItem.all" effect="plain" size="large">
{{ $t('commons.table.all') }} * {{ countItem.all }}
</el-tag>
<el-tag
@click="searchWithStatus('running')"
v-if="countItem.running"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.running') }} * {{ countItem.running }}
</el-tag>
<el-tag
@click="searchWithStatus('created')"
v-if="countItem.created"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.created') }} * {{ countItem.created }}
</el-tag>
<el-tag
@click="searchWithStatus('paused')"
v-if="countItem.paused"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.paused') }} * {{ countItem.paused }}
</el-tag>
<el-tag
@click="searchWithStatus('restarting')"
v-if="countItem.restarting"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.restarting') }} * {{ countItem.restarting }}
</el-tag>
<el-tag
@click="searchWithStatus('removing')"
v-if="countItem.removing"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.removing') }} * {{ countItem.removing }}
</el-tag>
<el-tag
@click="searchWithStatus('exited')"
v-if="countItem.exited"
effect="plain"
size="large"
class="ml-2"
>
{{ $t('commons.status.exited') }} * {{ countItem.exited }}
</el-tag>
<el-tag @click="searchWithStatus('dead')" v-if="countItem.dead" effect="plain" size="large" class="ml-2">
{{ $t('commons.status.dead') }} * {{ countItem.dead }}
</el-tag>
</div>
<LayoutContent :title="$t('menu.container')" v-if="isExist" :class="{ mask: !isActive }"> <LayoutContent :title="$t('menu.container')" v-if="isExist" :class="{ mask: !isActive }">
<template #search>
<div class="card-interval" v-if="isExist && isActive">
<div v-for="item in tags" :key="item.key" class="inline">
<el-button
v-if="item.count"
class="tag-button"
:class="activeTag === item.key ? '' : 'no-active'"
@click="searchWithStatus(item.key)"
:type="activeTag === item.key ? 'primary' : ''"
:plain="activeTag !== item.key"
>
{{ item.key === 'all' ? $t('commons.table.all') : $t('commons.status.' + item.key) }} *
{{ item.count }}
</el-button>
</div>
</div>
</template>
<template #leftToolBar> <template #leftToolBar>
<el-button type="primary" @click="onContainerOperate('')"> <el-button type="primary" @click="onContainerOperate('')">
{{ $t('container.create') }} {{ $t('container.create') }}
@ -102,7 +57,11 @@
<el-tooltip <el-tooltip
:content="includeAppStore ? $t('container.includeAppstore') : $t('container.excludeAppstore')" :content="includeAppStore ? $t('container.includeAppstore') : $t('container.excludeAppstore')"
> >
<el-button @click="searchWithAppShow(!includeAppStore)" :icon="includeAppStore ? 'View' : 'Hide'" /> <el-button
:type="includeAppStore ? '' : 'primary'"
@click="searchWithAppShow(!includeAppStore)"
:icon="includeAppStore ? 'View' : 'Hide'"
/>
</el-tooltip> </el-tooltip>
<TableRefresh @search="search()" /> <TableRefresh @search="search()" />
<TableSetting title="container-refresh" @search="refresh()" /> <TableSetting title="container-refresh" @search="refresh()" />
@ -455,16 +414,8 @@ const opRef = ref();
const includeAppStore = ref(); const includeAppStore = ref();
const columns = ref([]); const columns = ref([]);
const countItem = reactive({ const tags = ref([]);
all: 0, const activeTag = ref('all');
created: 0,
running: 0,
paused: 0,
restarting: 0,
removing: 0,
exited: 0,
dead: 0,
});
const goDashboard = async (port: any) => { const goDashboard = async (port: any) => {
if (port.indexOf('127.0.0.1') !== -1) { if (port.indexOf('127.0.0.1') !== -1) {
@ -527,8 +478,9 @@ const search = async (column?: any) => {
}); });
}; };
const searchWithStatus = (item: any) => { const searchWithStatus = (item: string) => {
paginationConfig.state = item; activeTag.value = item;
paginationConfig.state = activeTag.value;
search(); search();
}; };
@ -539,14 +491,15 @@ const searchWithAppShow = (item: any) => {
const loadContainerCount = async () => { const loadContainerCount = async () => {
await loadContainerStatus().then((res) => { await loadContainerStatus().then((res) => {
countItem.all = res.data.all; tags.value = [];
countItem.running = res.data.running; tags.value.push({ key: 'all', count: res.data.all });
countItem.paused = res.data.paused; tags.value.push({ key: 'running', count: res.data.running });
countItem.restarting = res.data.restarting; tags.value.push({ key: 'paused', count: res.data.paused });
countItem.removing = res.data.removing; tags.value.push({ key: 'restarting', count: res.data.restarting });
countItem.created = res.data.created; tags.value.push({ key: 'removing', count: res.data.removing });
countItem.dead = res.data.dead; tags.value.push({ key: 'created', count: res.data.created });
countItem.exited = res.data.exited; tags.value.push({ key: 'dead', count: res.data.dead });
tags.value.push({ key: 'exited', count: res.data.exited });
}); });
}; };
@ -810,4 +763,12 @@ onMounted(() => {
position: relative !important; position: relative !important;
z-index: 1 !important; z-index: 1 !important;
} }
.tag-button {
margin-top: -5px;
margin-right: 10px;
&.no-active {
background: none;
border: none;
}
}
</style> </style>

View file

@ -214,7 +214,7 @@
<el-card class="mt-5"> <el-card class="mt-5">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isWebsite()"> <LayoutCol v-if="isWebsite()">
<el-form-item <el-form-item
:label="form.type === 'website' ? $t('cronjob.website') : $t('menu.website')" :label="form.type === 'website' ? $t('cronjob.website') : $t('menu.website')"
prop="website" prop="website"
@ -241,8 +241,8 @@
{{ $t('cronjob.cutWebsiteLogHelper') }} {{ $t('cronjob.cutWebsiteLogHelper') }}
</span> </span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'app'"> <LayoutCol v-if="form.type === 'app'">
<el-form-item :label="$t('cronjob.app')" prop="appID"> <el-form-item :label="$t('cronjob.app')" prop="appID">
<el-select clearable v-model="form.appID"> <el-select clearable v-model="form.appID">
<el-option <el-option
@ -260,8 +260,8 @@
</div> </div>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'database'"> <LayoutCol v-if="form.type === 'database'">
<el-form-item :label="$t('cronjob.database')"> <el-form-item :label="$t('cronjob.database')">
<el-select v-model="form.dbType" @change="loadDatabases"> <el-select v-model="form.dbType" @change="loadDatabases">
<el-option label="MySQL" value="mysql" /> <el-option label="MySQL" value="mysql" />
@ -269,8 +269,8 @@
<el-option label="PostgreSQL" value="postgresql" /> <el-option label="PostgreSQL" value="postgresql" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'database'"> <LayoutCol v-if="form.type === 'database'">
<el-form-item :label="$t('cronjob.database')" prop="dbName"> <el-form-item :label="$t('cronjob.database')" prop="dbName">
<el-select clearable v-model="form.dbName"> <el-select clearable v-model="form.dbName">
<el-option <el-option
@ -298,34 +298,32 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'directory'"> <LayoutCol v-if="form.type === 'directory'">
<el-form-item :label="$t('cronjob.backupContent')"> <el-form-item :label="$t('cronjob.backupContent')">
<el-radio-group v-model="form.isDir"> <el-radio-group v-model="form.isDir" class="w-full">
<el-radio :value="true">{{ $t('file.dir') }}</el-radio> <el-radio :value="true">{{ $t('file.dir') }}</el-radio>
<el-radio :value="false">{{ $t('menu.files') }}</el-radio> <el-radio :value="false">{{ $t('menu.files') }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'curl'"> <LayoutCol v-if="form.type === 'curl'">
<el-form-item :label="$t('cronjob.url')" prop="url"> <el-form-item :label="$t('cronjob.url')" prop="url">
<el-input clearable v-model.trim="form.url" /> <el-input clearable v-model.trim="form.url" />
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row>
<div v-if="hasScript()" class="w-full"> <el-row :gutter="20">
<el-row :gutter="20"> <el-col :span="24" v-if="hasScript()">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <el-form-item>
<el-form-item> <el-checkbox v-model="form.inContainer">
<el-checkbox v-model="form.inContainer"> {{ $t('cronjob.containerCheckBox') }}
{{ $t('cronjob.containerCheckBox') }} </el-checkbox>
</el-checkbox> </el-form-item>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="form.inContainer"> <el-row :gutter="20" v-if="form.inContainer">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('cronjob.containerName')" prop="containerName"> <el-form-item :label="$t('cronjob.containerName')" prop="containerName">
<el-select v-model="form.containerName"> <el-select v-model="form.containerName">
<el-option <el-option
@ -336,8 +334,8 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item <el-form-item
:label="$t('container.command')" :label="$t('container.command')"
prop="command" prop="command"
@ -364,10 +362,10 @@
v-model="form.command" v-model="form.command"
/> />
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
<el-row :gutter="20" v-if="!form.inContainer"> <el-row :gutter="20" v-if="!form.inContainer">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('commons.table.user')" prop="user"> <el-form-item :label="$t('commons.table.user')" prop="user">
<el-select filterable v-model="form.user"> <el-select filterable v-model="form.user">
<div v-for="item in userOptions" :key="item"> <div v-for="item in userOptions" :key="item">
@ -375,8 +373,8 @@
</div> </div>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('cronjob.executor')" prop="executor"> <el-form-item :label="$t('cronjob.executor')" prop="executor">
<el-checkbox border v-model="form.isCustom"> <el-checkbox border v-model="form.isCustom">
{{ $t('container.custom') }} {{ $t('container.custom') }}
@ -397,39 +395,51 @@
v-model="form.executor" v-model="form.executor"
/> />
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
<el-form-item :label="$t('cronjob.shellContent')" prop="script" class="mt-5"> <el-form-item :label="$t('cronjob.shellContent')" prop="scriptMode">
<el-radio-group @change="form.script = ''" v-model="form.scriptMode"> <el-radio-group @change="form.script = ''" v-model="form.scriptMode">
<el-radio value="input">{{ $t('commons.button.edit') }}</el-radio> <el-radio value="input">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="library">{{ $t('cronjob.library.library') }}</el-radio>
<el-radio value="select">{{ $t('container.pathSelect') }}</el-radio> <el-radio value="select">{{ $t('container.pathSelect') }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item class="-mt-4" v-if="form.scriptMode === 'input'" prop="script">
<CodemirrorPro <CodemirrorPro
v-if="form.scriptMode === 'input'" v-model="form.script"
v-model="form.script" placeholder="#Define or paste the content of your shell file here"
placeholder="#Define or paste the content of your shell file here" mode="javascript"
mode="javascript" :heightDiff="400"
:heightDiff="400" />
class="mb-5" </el-form-item>
/> <el-row :gutter="20" class="-mt-4">
<el-row :gutter="20" v-if="form.scriptMode === 'select'"> <LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <el-form-item prop="scriptID" v-if="form.scriptMode === 'library'">
<el-input <el-select filterable v-model="form.scriptID">
:placeholder="$t('commons.example') + '/tmp/test.sh'" <el-option
v-model="form.script" v-for="item in scriptOptions"
> :key="item.id"
<template #prepend> :value="item.id"
<FileList @choose="loadScriptDir" :dir="false"></FileList> :label="item.name"
</template> />
</el-input> </el-select>
</el-col> </el-form-item>
<el-form-item prop="script" v-if="form.scriptMode === 'select'">
<el-input
:placeholder="$t('commons.example') + '/tmp/test.sh'"
v-model="form.script"
>
<template #prepend>
<FileList @choose="loadScriptDir" :dir="false"></FileList>
</template>
</el-input>
</el-form-item>
</LayoutCol>
</el-row> </el-row>
</div> </el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isDir() && form.isDir"> <LayoutCol v-if="isDir() && form.isDir">
<el-form-item prop="sourceDir"> <el-form-item prop="sourceDir">
<el-input v-model="form.sourceDir"> <el-input v-model="form.sourceDir">
<template #prepend> <template #prepend>
@ -437,8 +447,8 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isDir() && !form.isDir"> <LayoutCol v-if="isDir() && !form.isDir">
<el-input class="mb-5"> <el-input class="mb-5">
<template #prepend> <template #prepend>
<FileList @choose="loadFile" :dir="false" /> <FileList @choose="loadFile" :dir="false" />
@ -462,11 +472,11 @@
</ComplexTable> </ComplexTable>
</div> </div>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup()"> <LayoutCol v-if="isBackup()">
<el-form-item :label="$t('setting.backupAccount')" prop="sourceAccountItems"> <el-form-item :label="$t('setting.backupAccount')" prop="sourceAccountItems">
<el-select multiple v-model="form.sourceAccountItems" @change="changeAccount"> <el-select multiple v-model="form.sourceAccountItems" @change="changeAccount">
<div v-for="item in backupOptions" :key="item.id"> <div v-for="item in backupOptions" :key="item.id">
@ -493,8 +503,8 @@
</el-link> </el-link>
</span> </span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup()"> <LayoutCol v-if="isBackup()">
<el-form-item :label="$t('cronjob.default_download_path')" prop="downloadAccountID"> <el-form-item :label="$t('cronjob.default_download_path')" prop="downloadAccountID">
<el-select v-model="form.downloadAccountID"> <el-select v-model="form.downloadAccountID">
<div v-for="item in accountOptions" :key="item.id"> <div v-for="item in accountOptions" :key="item.id">
@ -510,16 +520,16 @@
</div> </div>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup() && !isDatabase()"> <LayoutCol v-if="isBackup() && !isDatabase()">
<el-form-item :label="$t('setting.compressPassword')" prop="secret"> <el-form-item :label="$t('setting.compressPassword')" prop="secret">
<el-input v-model="form.secret" /> <el-input v-model="form.secret" />
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies"> <el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
<el-input-number <el-input-number
class="selectClass" class="selectClass"
@ -533,10 +543,10 @@
</span> </span>
<span v-else class="input-help">{{ $t('cronjob.retainCopiesHelper') }}</span> <span v-else class="input-help">{{ $t('cronjob.retainCopiesHelper') }}</span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20" v-if="hasExclusionRules()"> <LayoutCol :span="20" v-if="hasExclusionRules()">
<el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules"> <el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
<el-input <el-input
:placeholder="$t('cronjob.rulesHelper')" :placeholder="$t('cronjob.rulesHelper')"
@ -545,14 +555,14 @@
/> />
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span> <span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
</el-card> </el-card>
<el-card class="mt-5"> <el-card class="mt-5">
<div v-if="!globalStore.isIntl"> <div v-if="!globalStore.isIntl">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item prop="hasAlert"> <el-form-item prop="hasAlert">
<el-checkbox v-model="form.hasAlert" :label="$t('xpack.alert.isAlert')" /> <el-checkbox v-model="form.hasAlert" :label="$t('xpack.alert.isAlert')" />
<span class="input-help">{{ $t('xpack.alert.cronJobHelper') }}</span> <span class="input-help">{{ $t('xpack.alert.cronJobHelper') }}</span>
@ -564,8 +574,8 @@
</el-link> </el-link>
</span> </span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item <el-form-item
prop="alertCount" prop="alertCount"
v-if="form.hasAlert && isProductPro" v-if="form.hasAlert && isProductPro"
@ -580,12 +590,12 @@
></el-input-number> ></el-input-number>
<span class="input-help">{{ $t('xpack.alert.alertCountHelper') }}</span> <span class="input-help">{{ $t('xpack.alert.alertCountHelper') }}</span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
</div> </div>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem"> <el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem">
<el-input type="number" class="selectClass" v-model.number="form.timeoutItem"> <el-input type="number" class="selectClass" v-model.number="form.timeoutItem">
<template #append> <template #append>
@ -597,8 +607,8 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10"> <LayoutCol>
<el-form-item :label="$t('cronjob.retryTimes')" prop="retryTimes"> <el-form-item :label="$t('cronjob.retryTimes')" prop="retryTimes">
<el-input-number <el-input-number
class="selectClass" class="selectClass"
@ -609,7 +619,7 @@
></el-input-number> ></el-input-number>
<span class="input-help">{{ $t('cronjob.retryTimesHelper') }}</span> <span class="input-help">{{ $t('cronjob.retryTimesHelper') }}</span>
</el-form-item> </el-form-item>
</el-col> </LayoutCol>
</el-row> </el-row>
</el-card> </el-card>
@ -638,8 +648,9 @@ import { listBackupOptions } from '@/api/modules/backup';
import i18n from '@/lang'; 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, loadCronjobInfo, loadNextHandle } from '@/api/modules/cronjob'; import { addCronjob, editCronjob, loadCronjobInfo, loadNextHandle, loadScriptOptions } from '@/api/modules/cronjob';
import CodemirrorPro from '@/components/codemirror-pro/index.vue'; import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import LayoutCol from '@/components/layout-col/form.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';
@ -802,6 +813,7 @@ const search = async () => {
loadShellUsers(); loadShellUsers();
loadWebsites(); loadWebsites();
loadContainers(); loadContainers();
loadScripts();
if (form.dbType) { if (form.dbType) {
loadDatabases(form.dbType); loadDatabases(form.dbType);
} else { } else {
@ -819,6 +831,7 @@ const backupOptions = ref([]);
const accountOptions = ref([]); const accountOptions = ref([]);
const appOptions = ref([]); const appOptions = ref([]);
const userOptions = ref([]); const userOptions = ref([]);
const scriptOptions = ref([]);
const dbInfo = reactive({ const dbInfo = reactive({
isExist: false, isExist: false,
@ -968,6 +981,7 @@ const rules = reactive({
], ],
script: [{ validator: verifyScript, trigger: 'blur', required: true }], script: [{ validator: verifyScript, trigger: 'blur', required: true }],
scriptID: [Rules.requiredSelect],
containerName: [Rules.requiredSelect], containerName: [Rules.requiredSelect],
appID: [Rules.requiredSelect], appID: [Rules.requiredSelect],
website: [Rules.requiredSelect], website: [Rules.requiredSelect],
@ -1040,6 +1054,11 @@ const loadNext = async (spec: any) => {
nextTimes.value = data.data || []; nextTimes.value = data.data || [];
}; };
const loadScripts = async () => {
const res = await loadScriptOptions();
scriptOptions.value = res.data || [];
};
const loadDatabases = async (dbType: string) => { const loadDatabases = async (dbType: string) => {
const data = await listDbItems(dbType); const data = await listDbItems(dbType);
dbInfo.dbs = data.data || []; dbInfo.dbs = data.data || [];
@ -1288,6 +1307,7 @@ onMounted(() => {
} }
.selectClass { .selectClass {
width: 100%; width: 100%;
padding-left: 0px;
} }
.tagClass { .tagClass {
float: right; float: right;

View file

@ -47,6 +47,14 @@
</el-text> </el-text>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('cronjob.library.isInteractive')" prop="isInteractive" min-width="60">
<template #default="{ row }">
<div class="-mb-1">
<el-icon v-if="row.isInteractive"><Check /></el-icon>
<el-icon v-else><Minus /></el-icon>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.group')" min-width="120" prop="group"> <el-table-column :label="$t('commons.table.group')" min-width="120" prop="group">
<template #default="{ row }"> <template #default="{ row }">
<el-button class="mr-3" size="small" v-if="row.isSystem">{{ $t('menu.system') }}</el-button> <el-button class="mr-3" size="small" v-if="row.isSystem">{{ $t('menu.system') }}</el-button>

View file

@ -8,7 +8,14 @@
> >
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" :rules="rules"> <el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" :rules="rules">
<el-form-item :label="$t('commons.table.name')" prop="name"> <el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="dialogData.rowData!.name" /> <el-tag v-if="dialogData.title === 'edit'">{{ dialogData.rowData!.name }}</el-tag>
<el-input v-else v-model="dialogData.rowData!.name" />
</el-form-item>
<el-form-item prop="isInteractive">
<el-checkbox v-model="dialogData.rowData!.isInteractive">
{{ $t('cronjob.library.interactive') }}
</el-checkbox>
<span class="input-help">{{ $t('cronjob.library.interactiveHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.table.group')" prop="groupList"> <el-form-item :label="$t('commons.table.group')" prop="groupList">
<el-select filterable v-model="dialogData.rowData!.groupList" multiple> <el-select filterable v-model="dialogData.rowData!.groupList" multiple>