diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index b46242d434e6..91fdf34ca4c0 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -19,7 +19,7 @@ import ( ) var AddTable = &gormigrate.Migration{ - ID: "20240108-add-table", + ID: "20250108-add-table", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate( &model.AppDetail{}, diff --git a/agent/utils/ssh/ssh.go b/agent/utils/ssh/ssh.go index f487182d4284..604d97d032a2 100644 --- a/agent/utils/ssh/ssh.go +++ b/agent/utils/ssh/ssh.go @@ -1,11 +1,8 @@ package ssh import ( - "bytes" "fmt" - "io" "strings" - "sync" "time" gossh "golang.org/x/crypto/ssh" @@ -82,58 +79,6 @@ func (c *ConnInfo) Close() { _ = c.Client.Close() } -type SshConn struct { - StdinPipe io.WriteCloser - ComboOutput *wsBufferWriter - Session *gossh.Session -} - -func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) { - sshSession, err := c.Client.NewSession() - if err != nil { - return nil, err - } - - stdinP, err := sshSession.StdinPipe() - if err != nil { - return nil, err - } - - comboWriter := new(wsBufferWriter) - sshSession.Stdout = comboWriter - sshSession.Stderr = comboWriter - - modes := gossh.TerminalModes{ - gossh.ECHO: 1, - gossh.TTY_OP_ISPEED: 14400, - gossh.TTY_OP_OSPEED: 14400, - } - if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { - return nil, err - } - if err := sshSession.Shell(); err != nil { - return nil, err - } - return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil -} - -func (s *SshConn) Close() { - if s.Session != nil { - s.Session.Close() - } -} - -type wsBufferWriter struct { - buffer bytes.Buffer - mu sync.Mutex -} - -func (w *wsBufferWriter) Write(p []byte) (int, error) { - w.mu.Lock() - defer w.mu.Unlock() - return w.buffer.Write(p) -} - func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { if len(passPhrase) != 0 { return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) diff --git a/core/app/api/v2/entry.go b/core/app/api/v2/entry.go index b5de17ea58c0..e25d3cb177ee 100644 --- a/core/app/api/v2/entry.go +++ b/core/app/api/v2/entry.go @@ -18,4 +18,5 @@ var ( groupService = service.NewIGroupService() commandService = service.NewICommandService() appLauncherService = service.NewIAppLauncher() + scriptService = service.NewIScriptService() ) diff --git a/core/app/api/v2/host.go b/core/app/api/v2/host.go index a299fc5536eb..d7dee0c2823b 100644 --- a/core/app/api/v2/host.go +++ b/core/app/api/v2/host.go @@ -111,13 +111,13 @@ func (b *BaseApi) HostTree(c *gin.Context) { // @Tags Host // @Summary Page host // @Accept json -// @Param request body dto.SearchHostWithPage true "request" +// @Param request body dto.SearchPageWithGroup true "request" // @Success 200 {object} dto.PageResult // @Security ApiKeyAuth // @Security Timestamp // @Router /core/hosts/search [post] func (b *BaseApi) SearchHost(c *gin.Context) { - var req dto.SearchHostWithPage + var req dto.SearchPageWithGroup if err := helper.CheckBindAndValidate(&req, c); err != nil { return } @@ -304,7 +304,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) { return } defer client.Close() - sws, err := terminal.NewLogicSshWsSession(cols, rows, true, client.Client, wsConn) + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "") if wshandleError(wsConn, err) { return } diff --git a/core/app/api/v2/script_library.go b/core/app/api/v2/script_library.go new file mode 100644 index 000000000000..dccd45ea83e6 --- /dev/null +++ b/core/app/api/v2/script_library.go @@ -0,0 +1,194 @@ +package v2 + +import ( + "fmt" + "path" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/ssh" + "github.com/1Panel-dev/1Panel/core/utils/terminal" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +// @Tags ScriptLibrary +// @Summary Add script +// @Accept json +// @Param request body dto.ScriptOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /script [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [name]","formatEN":"add script [name]"} +func (b *BaseApi) CreateScript(c *gin.Context) { + var req dto.ScriptOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags ScriptLibrary +// @Summary Page script +// @Accept json +// @Param request body dto.SearchPageWithGroup true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /script/search [post] +func (b *BaseApi) SearchScript(c *gin.Context) { + var req dto.SearchPageWithGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := scriptService.Search(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags ScriptLibrary +// @Summary Delete script +// @Accept json +// @Param request body dto.BatchDeleteReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /script/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"script_librarys","output_column":"name","output_value":"names"}],"formatZH":"删除脚本库脚本 [names]","formatEN":"delete script [names]"} +func (b *BaseApi) DeleteScript(c *gin.Context) { + var req dto.OperateByIDs + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags ScriptLibrary +// @Summary Update script +// @Accept json +// @Param request body dto.ScriptOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /script/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 script [name]"} +func (b *BaseApi) UpdateScript(c *gin.Context) { + var req dto.ScriptOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithOutData(c) +} + +func (b *BaseApi) RunScript(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + scriptID := c.Query("script_id") + currentNode := c.Query("current_node") + intNum, _ := strconv.Atoi(scriptID) + if intNum == 0 { + if wshandleError(wsConn, fmt.Errorf(" no such script %v in library, please check and try again!", scriptID)) { + return + } + } + scriptItem, err := service.LoadScriptInfo(uint(intNum)) + if wshandleError(wsConn, err) { + return + } + + fileName := strings.ReplaceAll(scriptItem.Name, " ", "_") + quitChan := make(chan bool, 3) + if currentNode == "local" { + tmpFile := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script") + initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName) + slave, err := terminal.NewCommand(initCmd) + if wshandleError(wsConn, err) { + return + } + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + tty.Start(quitChan) + go slave.Wait(quitChan) + } else { + connInfo, installDir, err := xpack.LoadNodeInfo(currentNode) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + tmpFile := path.Join(installDir, "1panel/tmp/script") + initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName) + client, err := ssh.NewClient(*connInfo) + if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { + return + } + defer client.Close() + + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, initCmd) + if wshandleError(wsConn, err) { + return + } + defer sws.Close() + sws.Start(quitChan) + go sws.Wait(quitChan) + } + + <-quitChan + + global.LOG.Info("websocket finished") + if wshandleError(wsConn, err) { + return + } +} diff --git a/core/app/dto/command.go b/core/app/dto/command.go index 261624427d02..275e3b180eb2 100644 --- a/core/app/dto/command.go +++ b/core/app/dto/command.go @@ -5,13 +5,13 @@ type SearchCommandWithPage struct { OrderBy string `json:"orderBy" validate:"required,oneof=name command createdAt"` Order string `json:"order" validate:"required,oneof=null ascending descending"` GroupID uint `json:"groupID"` - Type string `josn:"type" validate:"required,oneof=redis command"` + Type string `json:"type" validate:"required,oneof=redis command"` Info string `json:"info"` } type CommandOperate struct { ID uint `json:"id"` - Type string `josn:"type"` + Type string `json:"type"` GroupID uint `json:"groupID"` GroupBelong string `json:"groupBelong"` Name string `json:"name" validate:"required"` diff --git a/core/app/dto/common.go b/core/app/dto/common.go index acdaa078281e..5284320407d2 100644 --- a/core/app/dto/common.go +++ b/core/app/dto/common.go @@ -11,6 +11,12 @@ type SearchPageWithType struct { Info string `json:"info"` } +type SearchPageWithGroup struct { + PageInfo + GroupID uint `json:"groupID"` + Info string `json:"info"` +} + type PageInfo struct { Page int `json:"page" validate:"required,number"` PageSize int `json:"pageSize" validate:"required,number"` diff --git a/core/app/dto/host.go b/core/app/dto/host.go index 9853d8f34a50..04e3c3f615e3 100644 --- a/core/app/dto/host.go +++ b/core/app/dto/host.go @@ -30,12 +30,6 @@ type HostConnTest struct { PassPhrase string `json:"passPhrase"` } -type SearchHostWithPage struct { - PageInfo - GroupID uint `json:"groupID"` - Info string `json:"info"` -} - type SearchForTree struct { Info string `json:"info"` } diff --git a/core/app/dto/script_library.go b/core/app/dto/script_library.go new file mode 100644 index 000000000000..b9eaf46423c5 --- /dev/null +++ b/core/app/dto/script_library.go @@ -0,0 +1,23 @@ +package dto + +import "time" + +type ScriptInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Lable string `json:"lable"` + Script string `json:"script"` + GroupList []uint `json:"groupList"` + GroupBelong []string `json:"groupBelong"` + IsSystem bool `json:"isSystem"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` +} + +type ScriptOperate struct { + ID uint `json:"id"` + Name string `json:"name"` + Script string `json:"script"` + Groups string `json:"groups"` + Description string `json:"description"` +} diff --git a/core/app/model/script_library.go b/core/app/model/script_library.go new file mode 100644 index 000000000000..91061a316016 --- /dev/null +++ b/core/app/model/script_library.go @@ -0,0 +1,11 @@ +package model + +type ScriptLibrary struct { + BaseModel + Name string `json:"name" gorm:"not null;"` + Lable string `json:"lable"` + Script string `json:"script" gorm:"not null;"` + Groups string `json:"groups"` + IsSystem bool `json:"isSystem"` + Description string `json:"description"` +} diff --git a/core/app/repo/script_library.go b/core/app/repo/script_library.go new file mode 100644 index 000000000000..9a132724eb92 --- /dev/null +++ b/core/app/repo/script_library.go @@ -0,0 +1,78 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type IScriptRepo interface { + Get(opts ...global.DBOption) (model.ScriptLibrary, error) + GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) + Create(snap *model.ScriptLibrary) error + Update(id uint, vars map[string]interface{}) error + Page(limit, offset int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) + Delete(opts ...global.DBOption) error + + WithByInfo(info string) global.DBOption +} + +func NewIScriptRepo() IScriptRepo { + return &ScriptRepo{} +} + +type ScriptRepo struct{} + +func (u *ScriptRepo) Get(opts ...global.DBOption) (model.ScriptLibrary, error) { + var ScriptLibrary model.ScriptLibrary + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&ScriptLibrary).Error + return ScriptLibrary, err +} + +func (u *ScriptRepo) GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) { + var snaps []model.ScriptLibrary + db := global.DB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&snaps).Error + return snaps, err +} + +func (u *ScriptRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) { + var users []model.ScriptLibrary + db := global.DB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *ScriptRepo) Create(ScriptLibrary *model.ScriptLibrary) error { + return global.DB.Create(ScriptLibrary).Error +} + +func (u *ScriptRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.ScriptLibrary{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *ScriptRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.ScriptLibrary{}).Error +} + +func (u *ScriptRepo) WithByInfo(info string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("name LIKE ? OR description LIKE ?", "%"+info+"%", "%"+info+"%") + } +} diff --git a/core/app/service/entry.go b/core/app/service/entry.go index d2dcc227b2eb..c29b25ed22e3 100644 --- a/core/app/service/entry.go +++ b/core/app/service/entry.go @@ -12,7 +12,7 @@ var ( launcherRepo = repo.NewILauncherRepo() upgradeLogRepo = repo.NewIUpgradeLogRepo() - taskRepo = repo.NewITaskRepo() - - agentRepo = repo.NewIAgentRepo() + taskRepo = repo.NewITaskRepo() + agentRepo = repo.NewIAgentRepo() + scriptRepo = repo.NewIScriptRepo() ) diff --git a/core/app/service/group.go b/core/app/service/group.go index c693c5735f30..51a53ac5e1bc 100644 --- a/core/app/service/group.go +++ b/core/app/service/group.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "net/http" + "strconv" + "strings" "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" @@ -74,6 +76,22 @@ func (u *GroupService) Delete(id uint) error { if group.ID == 0 { return buserr.New("ErrRecordNotFound") } + if group.Type == "script" { + list, _ := scriptRepo.GetList() + if len(list) == 0 { + return groupRepo.Delete(repo.WithByID(id)) + } + for _, itemData := range list { + groupIDs := strings.Split(itemData.Groups, ",") + for _, idItem := range groupIDs { + groupID, _ := strconv.Atoi(idItem) + if uint(groupID) == id { + return buserr.New("ErrGroupIsInUse") + } + } + } + return groupRepo.Delete(repo.WithByID(id)) + } if group.IsDefault { return buserr.New("ErrGroupIsDefault") } @@ -84,6 +102,8 @@ func (u *GroupService) Delete(id uint) error { switch group.Type { case "host": err = hostRepo.UpdateGroup(id, defaultGroup.ID) + case "script": + err = hostRepo.UpdateGroup(id, defaultGroup.ID) case "command": err = commandRepo.UpdateGroup(id, defaultGroup.ID) case "node": diff --git a/core/app/service/host.go b/core/app/service/host.go index 0e08a9cb3500..b8dd29b09b37 100644 --- a/core/app/service/host.go +++ b/core/app/service/host.go @@ -22,7 +22,7 @@ type IHostService interface { TestByInfo(req dto.HostConnTest) bool GetHostByID(id uint) (*dto.HostInfo, error) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) - SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) + SearchWithPage(search dto.SearchPageWithGroup) (int64, interface{}, error) Create(hostDto dto.HostOperate) (*dto.HostInfo, error) Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error) Delete(id []uint) error @@ -124,7 +124,7 @@ func (u *HostService) TestLocalConn(id uint) bool { return true } -func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interface{}, error) { +func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interface{}, error) { var options []global.DBOption if len(req.Info) != 0 { options = append(options, hostRepo.WithByInfo(req.Info)) diff --git a/core/app/service/script_library.go b/core/app/service/script_library.go new file mode 100644 index 000000000000..3262170b3da7 --- /dev/null +++ b/core/app/service/script_library.go @@ -0,0 +1,118 @@ +package service + +import ( + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/jinzhu/copier" +) + +type ScriptService struct{} + +type IScriptService interface { + Search(req dto.SearchPageWithGroup) (int64, interface{}, error) + Create(req dto.ScriptOperate) error + Update(req dto.ScriptOperate) error + Delete(ids dto.OperateByIDs) error +} + +func NewIScriptService() IScriptService { + return &ScriptService{} +} + +func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, error) { + options := []global.DBOption{repo.WithOrderBy("created_at desc")} + if len(req.Info) != 0 { + options = append(options, scriptRepo.WithByInfo(req.Info)) + } + list, err := scriptRepo.GetList(options...) + if err != nil { + return 0, nil, err + } + groups, _ := groupRepo.GetList(repo.WithByType("script")) + groupMap := make(map[uint]string) + for _, item := range groups { + groupMap[item.ID] = item.Name + } + var data []dto.ScriptInfo + for _, itemData := range list { + var item dto.ScriptInfo + if err := copier.Copy(&item, &itemData); err != nil { + global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) + } + matchGourp := false + groupIDs := strings.Split(itemData.Groups, ",") + for _, idItem := range groupIDs { + id, _ := strconv.Atoi(idItem) + if id == 0 { + continue + } + if uint(id) == req.GroupID { + matchGourp = true + } + item.GroupList = append(item.GroupList, uint(id)) + item.GroupBelong = append(item.GroupBelong, groupMap[uint(id)]) + } + if req.GroupID == 0 { + data = append(data, item) + continue + } + if matchGourp { + data = append(data, item) + } + } + var records []dto.ScriptInfo + total, start, end := len(data), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]dto.ScriptInfo, 0) + } else { + if end >= total { + end = total + } + records = data[start:end] + } + return int64(total), records, nil +} + +func (u *ScriptService) Create(req dto.ScriptOperate) error { + itemData, _ := scriptRepo.Get(repo.WithByName(req.Name)) + if itemData.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&itemData, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := scriptRepo.Create(&itemData); err != nil { + return err + } + return nil +} + +func (u *ScriptService) Delete(req dto.OperateByIDs) error { + return scriptRepo.Delete(repo.WithByIDs(req.IDs)) +} + +func (u *ScriptService) Update(req dto.ScriptOperate) error { + itemData, _ := scriptRepo.Get(repo.WithByID(req.ID)) + if itemData.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + updateMap := make(map[string]interface{}) + updateMap["name"] = req.Name + updateMap["script"] = req.Script + updateMap["groups"] = req.Groups + updateMap["description"] = req.Description + if err := scriptRepo.Update(req.ID, updateMap); err != nil { + return err + } + return nil +} + +func LoadScriptInfo(id uint) (model.ScriptLibrary, error) { + return scriptRepo.Get(repo.WithByID(id)) +} diff --git a/core/go.mod b/core/go.mod index 0b381e4a3b3b..77625c5e5005 100644 --- a/core/go.mod +++ b/core/go.mod @@ -5,7 +5,7 @@ go 1.23 require ( github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/aws/aws-sdk-go v1.55.5 - github.com/creack/pty v1.1.9 + github.com/creack/pty v1.1.21 github.com/fsnotify/fsnotify v1.7.0 github.com/gin-contrib/gzip v1.0.1 github.com/gin-gonic/gin v1.10.0 diff --git a/core/go.sum b/core/go.sum index 4b8b61724b1e..a08325f5defb 100644 --- a/core/go.sum +++ b/core/go.sum @@ -70,8 +70,9 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 12f8f3646ae2..872cc1c37509 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "Demo server, this operation is prohibited!" ErrCmdTimeout: "Command execution timeout!" ErrEntrance: "Security entrance information error, please check and try again!" ErrGroupIsDefault: "Default group, unable to delete" +ErrGroupIsInUse: "The group is in use and cannot be deleted." ErrLocalDelete: "Cannot delete the local node!" ErrPortInUsed: "The {{ .name }} port is already in use!" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index 99ddf2e2fe29..0ad9dabf0aba 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "デモサーバーではこの操作は許可されてい ErrCmdTimeout: "コマンドの実行がタイムアウトしました!" ErrEntrance: "セキュリティ情報エラー、再確認してください!" ErrGroupIsDefault: "デフォルトグループの削除はできません" +ErrGroupIsInUse: "グループは使用中のため、削除できません。" ErrLocalDelete: "ローカルノードは削除できません!" ErrPortInUsed: "{{ .name }} ポートはすでに使用されています!" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index bf324212c3a8..3aa5c550d821 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "데모 서버에서는 이 작업이 금지되어 있습니 ErrCmdTimeout: "명령 실행 시간 초과!" ErrEntrance: "보안 정보 오류입니다. 확인 후 다시 시도하십시오!" ErrGroupIsDefault: "기본 그룹은 삭제할 수 없습니다" +ErrGroupIsInUse: "그룹이 사용 중이므로 삭제할 수 없습니다." ErrLocalDelete: "로컬 노드는 삭제할 수 없습니다!" ErrPortInUsed: "{{ .name }} 포트가 이미 사용 중입니다!" diff --git a/core/i18n/lang/ms.yml b/core/i18n/lang/ms.yml index 52cdf44a7a87..e1434a95d20d 100644 --- a/core/i18n/lang/ms.yml +++ b/core/i18n/lang/ms.yml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "Pelayan demo, operasi ini dilarang!" ErrCmdTimeout: "Perintah telah tamat masa!" ErrEntrance: "Maklumat pintu masuk keselamatan salah, sila periksa dan cuba lagi!" ErrGroupIsDefault: "Kumpulan lalai tidak boleh dihapuskan" +ErrGroupIsInUse: "Kumpulan sedang digunakan dan tidak boleh dipadam." ErrLocalDelete: "Nod tempatan tidak boleh dihapuskan!" ErrPortInUsed: "Port {{ .name }} telah digunakan!" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index 6399d316410d..a244b42b395d 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "Servidor de demonstração, essa operação é proibida!" ErrCmdTimeout: "Tempo de execução do comando esgotado!" ErrEntrance: "Erro nas informações de entrada de segurança, por favor, verifique e tente novamente!" ErrGroupIsDefault: "Grupo padrão não pode ser excluído" +ErrGroupIsInUse: "O grupo está em uso e não pode ser excluído." ErrLocalDelete: "O nó local não pode ser excluído!" ErrPortInUsed: "A porta {{ .name }} já está em uso!" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index 70be0226e836..5509f2bf143c 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "Демонстрационный сервер, эта оп ErrCmdTimeout: "Время выполнения команды истекло!" ErrEntrance: "Ошибка информации о безопасном входе, проверьте и повторите попытку!" ErrGroupIsDefault: "Группу по умолчанию нельзя удалить" +ErrGroupIsInUse: "Группа используется и не может быть удалена." ErrLocalDelete: "Локальный узел нельзя удалить!" ErrPortInUsed: "Порт {{ .name }} уже используется!" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index b925d01efeaf..e77890367a0c 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "演示伺服器,禁止此操作!" ErrCmdTimeout: "指令執行逾時!" ErrEntrance: "安全入口資訊錯誤,請檢查後再試!" ErrGroupIsDefault: "預設分組無法刪除" +ErrGroupIsInUse: "分組正被使用,無法刪除。" ErrLocalDelete: "無法刪除本地節點!" ErrPortInUsed: "{{ .name }} 埠已被佔用!" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 9bfb77dfe73e..7ee8d27b6452 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -27,6 +27,7 @@ ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrCmdTimeout: "命令执行超时!" ErrEntrance: "安全入口信息错误,请检查后重试!" ErrGroupIsDefault: "默认分组,无法删除" +ErrGroupIsInUse: "分组正被使用,无法删除" ErrLocalDelete: "无法删除本地节点!" ErrPortInUsed: "{{ .name }} 端口已被占用!" diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 0b6151feddee..3d6a98395158 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -23,6 +23,7 @@ func Init() { migrations.AddMFAInterval, migrations.UpdateXpackHideMemu, migrations.AddSystemIP, + migrations.InitScriptLibrary, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index a8f25b7d0c33..5241c03a6824 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -328,3 +328,21 @@ var AddSystemIP = &gormigrate.Migration{ return nil }, } + +var InitScriptLibrary = &gormigrate.Migration{ + ID: "20250303-init-script-library", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.ScriptLibrary{}); err != nil { + return err + } + // defaultGroup := []model.Group{ + // {Name: "docker", Type: "script", IsDefault: false}, + // {Name: "install", Type: "script", IsDefault: false}, + // {Name: "uninstall", Type: "script", IsDefault: false}, + // } + // if err := tx.Create(&defaultGroup).Error; err != nil { + // return err + // } + return nil + }, +} diff --git a/core/router/common.go b/core/router/common.go index cfb82012f8cd..f28051b684e5 100644 --- a/core/router/common.go +++ b/core/router/common.go @@ -10,5 +10,6 @@ func commonGroups() []CommonRouter { &HostRouter{}, &GroupRouter{}, &AppLauncherRouter{}, + &ScriptRouter{}, } } diff --git a/core/router/ro_script_library.go b/core/router/ro_script_library.go new file mode 100644 index 000000000000..d04dfce5eea4 --- /dev/null +++ b/core/router/ro_script_library.go @@ -0,0 +1,24 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type ScriptRouter struct{} + +func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) { + scriptRouter := Router.Group("script"). + Use(middleware.JwtAuth()). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + scriptRouter.POST("", baseApi.CreateScript) + scriptRouter.POST("/search", baseApi.SearchScript) + scriptRouter.POST("/del", baseApi.DeleteScript) + scriptRouter.POST("/update", baseApi.UpdateScript) + scriptRouter.GET("/run", baseApi.RunScript) + } +} diff --git a/core/utils/ssh/ssh.go b/core/utils/ssh/ssh.go index b81cb6736df0..d0140ed79551 100644 --- a/core/utils/ssh/ssh.go +++ b/core/utils/ssh/ssh.go @@ -1,11 +1,8 @@ package ssh import ( - "bytes" "fmt" - "io" "strings" - "sync" "time" gossh "golang.org/x/crypto/ssh" @@ -93,58 +90,6 @@ func (c *SSHClient) Close() { _ = c.Client.Close() } -type SshConn struct { - StdinPipe io.WriteCloser - ComboOutput *wsBufferWriter - Session *gossh.Session -} - -func (c *SSHClient) NewSshConn(cols, rows int) (*SshConn, error) { - sshSession, err := c.Client.NewSession() - if err != nil { - return nil, err - } - - stdinP, err := sshSession.StdinPipe() - if err != nil { - return nil, err - } - - comboWriter := new(wsBufferWriter) - sshSession.Stdout = comboWriter - sshSession.Stderr = comboWriter - - modes := gossh.TerminalModes{ - gossh.ECHO: 1, - gossh.TTY_OP_ISPEED: 14400, - gossh.TTY_OP_OSPEED: 14400, - } - if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { - return nil, err - } - if err := sshSession.Shell(); err != nil { - return nil, err - } - return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil -} - -func (s *SshConn) Close() { - if s.Session != nil { - s.Session.Close() - } -} - -type wsBufferWriter struct { - buffer bytes.Buffer - mu sync.Mutex -} - -func (w *wsBufferWriter) Write(p []byte) (int, error) { - w.mu.Lock() - defer w.mu.Unlock() - return w.buffer.Write(p) -} - func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { if len(passPhrase) != 0 { return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) diff --git a/core/utils/terminal/local_cmd.go b/core/utils/terminal/local_cmd.go index 56f023a29fd0..c4ce047915b8 100644 --- a/core/utils/terminal/local_cmd.go +++ b/core/utils/terminal/local_cmd.go @@ -25,13 +25,21 @@ type LocalCommand struct { pty *os.File } -func NewCommand(commands []string) (*LocalCommand, error) { - cmd := exec.Command("docker", commands...) - +func NewCommand(initCmd string) (*LocalCommand, error) { + cmd := exec.Command("bash") + if term := os.Getenv("TERM"); term != "" { + cmd.Env = append(os.Environ(), "TERM="+term) + } else { + cmd.Env = append(os.Environ(), "TERM=xterm") + } pty, err := pty.Start(cmd) if err != nil { return nil, errors.Wrapf(err, "failed to start command") } + if len(initCmd) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = pty.Write([]byte(initCmd + "\n")) + } lcmd := &LocalCommand{ closeSignal: DefaultCloseSignal, diff --git a/core/utils/terminal/ws_session.go b/core/utils/terminal/ws_session.go index c278ba947a7d..98ba47f2acf2 100644 --- a/core/utils/terminal/ws_session.go +++ b/core/utils/terminal/ws_session.go @@ -59,7 +59,7 @@ type LogicSshWsSession struct { IsFlagged bool } -func NewLogicSshWsSession(cols, rows int, isAdmin bool, sshClient *ssh.Client, wsConn *websocket.Conn) (*LogicSshWsSession, error) { +func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websocket.Conn, initCmd string) (*LogicSshWsSession, error) { sshSession, err := sshClient.NewSession() if err != nil { return nil, err @@ -87,6 +87,10 @@ func NewLogicSshWsSession(cols, rows int, isAdmin bool, sshClient *ssh.Client, w if err := sshSession.Shell(); err != nil { return nil, err } + if len(initCmd) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = stdinP.Write([]byte(initCmd + "\n")) + } return &LogicSshWsSession{ stdinPipe: stdinP, comboOutput: comboWriter, @@ -94,7 +98,7 @@ func NewLogicSshWsSession(cols, rows int, isAdmin bool, sshClient *ssh.Client, w inputFilterBuff: inputBuf, session: sshSession, wsConn: wsConn, - isAdmin: isAdmin, + isAdmin: true, IsFlagged: false, }, nil } @@ -110,6 +114,7 @@ func (sws *LogicSshWsSession) Close() { sws.comboOutput = nil } } + func (sws *LogicSshWsSession) Start(quitChan chan bool) { go sws.receiveWsMsg(quitChan) go sws.sendComboOutput(quitChan) diff --git a/core/utils/xpack/xpack.go b/core/utils/xpack/xpack.go index 6229d8af1967..65bf8f9670c7 100644 --- a/core/utils/xpack/xpack.go +++ b/core/utils/xpack/xpack.go @@ -9,6 +9,7 @@ import ( "net/http" "time" + "github.com/1Panel-dev/1Panel/core/utils/ssh" "github.com/gin-gonic/gin" ) @@ -32,3 +33,7 @@ func LoadRequestTransport() *http.Transport { IdleConnTimeout: 15 * time.Second, } } + +func LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error) { + return nil, "", nil +} diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index cd7b7716567c..e17125428521 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -120,4 +120,22 @@ export namespace Cronjob { targetPath: string; interval: number; } + + export interface ScriptInfo { + id: number; + name: string; + script: string; + groups: string; + groupList: Array; + groupBelong: Array; + description: string; + createdAt: Date; + } + export interface ScriptOperate { + id: number; + name: string; + script: string; + groups: string; + description: string; + } } diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts index 47b8baf0a224..a05ddeb5562f 100644 --- a/frontend/src/api/modules/cronjob.ts +++ b/frontend/src/api/modules/cronjob.ts @@ -56,3 +56,16 @@ export const downloadRecord = (params: Cronjob.Download) => { export const handleOnce = (id: number) => { return http.post(`cronjobs/handle`, { id: id }); }; + +export const searchScript = (params: SearchWithPage) => { + return http.post>(`/core/script/search`, params); +}; +export const addScript = (params: Cronjob.ScriptOperate) => { + return http.post(`/core/script`, params); +}; +export const editScript = (params: Cronjob.ScriptOperate) => { + return http.post(`/core/script/update`, params); +}; +export const deleteScript = (ids: Array) => { + return http.post(`/core/script/del`, { ids: ids }); +}; diff --git a/frontend/src/components/group/index.vue b/frontend/src/components/group/index.vue index bb4d16c45db4..05a394ea74bc 100644 --- a/frontend/src/components/group/index.vue +++ b/frontend/src/components/group/index.vue @@ -52,7 +52,7 @@ @@ -75,6 +75,7 @@ import { Rules } from '@/global/form-rules'; import { FormInstance } from 'element-plus'; const open = ref(false); +const hideDefaultButton = ref(false); const type = ref(); const data = ref(); const handleClose = () => { @@ -84,10 +85,12 @@ const handleClose = () => { }; interface DialogProps { type: string; + hideDefaultButton: boolean; } const groupForm = ref(); const acceptParams = (params: DialogProps): void => { + hideDefaultButton.value = params.hideDefaultButton; type.value = params.type; open.value = true; search(); diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index de4587842498..98ebefb13cb1 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1033,6 +1033,12 @@ const message = { requestExpirationTime: 'Upload Request Expiration Time(Hours)', unitHours: 'Unit: Hours', alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert', + library: { + script: 'Script', + library: 'Script Library', + create: 'Add Script', + edit: 'Edit Script', + }, }, monitor: { globalFilter: 'Global Filter', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 724bbe7a01b2..d9009f07efe7 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -994,6 +994,12 @@ const message = { requestExpirationTime: 'リクエストの有効期限(時間)のアップロード', unitHours: 'ユニット:時間', alertTitle: '計画タスク - {0}「{1}」タスク障害アラート', + library: { + script: 'スクリプト', + library: 'スクリプトライブラリ', + create: 'スクリプトを追加', + edit: 'スクリプトを編集', + }, }, monitor: { globalFilter: 'グローバルフィルター', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index b5a2c95e6a56..bddd4a5eb4c6 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -987,6 +987,12 @@ const message = { requestExpirationTime: '업로드 요청 만료 시간(시간)', unitHours: '단위: 시간', alertTitle: '예정된 작업 - {0} 「{1}」 작업 실패 경고', + library: { + script: '스크립트', + library: '스크립트 라이브러리', + create: '스크립트 추가', + edit: '스크립트 수정', + }, }, monitor: { globalFilter: '전역 필터', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 2be941c6f3b2..f1c4bb8659d7 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1023,6 +1023,12 @@ const message = { requestExpirationTime: 'Waktu luput permintaan muat naik (Jam)', unitHours: 'Unit: Jam', alertTitle: 'Tugas Terancang - {0} 「{1}」 Amaran Kegagalan Tugas', + library: { + script: 'Skrip', + library: 'Perpustakaan Skrip', + create: 'Tambah Skrip', + edit: 'Sunting Skrip', + }, }, monitor: { globalFilter: 'Penapis Global', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 4bf0c5a3c625..4a0ad7911260 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1012,6 +1012,12 @@ const message = { requestExpirationTime: 'Tempo de expiração da solicitação de upload (Horas)', unitHours: 'Unidade: Horas', alertTitle: 'Tarefa Planejada - {0} 「{1}」 Alerta de Falha na Tarefa', + library: { + script: 'Script', + library: 'Biblioteca de Scripts', + create: 'Adicionar Script', + edit: 'Editar Script', + }, }, monitor: { globalFilter: 'Filtro global', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 95de001b4dda..1e75195b9e2f 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1017,6 +1017,12 @@ const message = { requestExpirationTime: 'Время истечения запроса на загрузку (часы)', unitHours: 'Единица: часы', alertTitle: 'Плановая задача - {0} «{1}» Оповещение о сбое задачи', + library: { + script: 'Скрипт', + library: 'Библиотека скриптов', + create: 'Добавить скрипт', + edit: 'Редактировать скрипт', + }, }, monitor: { globalFilter: 'Глобальный фильтр', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 5eb0feeeec57..fbddab64726b 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -980,6 +980,12 @@ const message = { requestExpirationTime: '上傳請求過期時間(小時)', unitHours: '單位:小時', alertTitle: '計畫任務-{0}「{1}」任務失敗告警', + library: { + script: '腳本', + library: '腳本庫', + create: '添加腳本', + edit: '修改腳本', + }, }, monitor: { globalFilter: '全局過濾', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index bc1ebd1f36f8..e96a69b5ceb2 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -978,6 +978,12 @@ const message = { requestExpirationTime: '上传请求过期时间(小时)', unitHours: '单位:小时', alertTitle: '计划任务-{0}「 {1} 」任务失败告警', + library: { + script: '脚本', + library: '脚本库', + create: '添加脚本', + edit: '修改脚本', + }, }, monitor: { globalFilter: '全局过滤', diff --git a/frontend/src/routers/modules/cronjob.ts b/frontend/src/routers/modules/cronjob.ts index e2726b634ab9..774d3f30703e 100644 --- a/frontend/src/routers/modules/cronjob.ts +++ b/frontend/src/routers/modules/cronjob.ts @@ -5,7 +5,7 @@ const cronRouter = { path: '/cronjobs', name: 'Cronjob-Menu', component: Layout, - redirect: '/cronjobs', + redirect: '/cronjobs/cronjob', meta: { icon: 'p-plan', title: 'menu.cronjob', @@ -14,10 +14,31 @@ const cronRouter = { { path: '/cronjobs', name: 'Cronjob', + redirect: '/cronjobs/cronjob', component: () => import('@/views/cronjob/index.vue'), - meta: { - requiresAuth: false, - }, + meta: {}, + children: [ + { + path: 'cronjob', + name: 'CronjobItem', + component: () => import('@/views/cronjob/cronjob/index.vue'), + hidden: true, + meta: { + activeMenu: '/cronjobs', + requiresAuth: false, + }, + }, + { + path: 'library', + name: 'Library', + component: () => import('@/views/cronjob/library/index.vue'), + hidden: true, + meta: { + activeMenu: '/cronjobs', + requiresAuth: false, + }, + }, + ], }, ], }; diff --git a/frontend/src/views/cronjob/backup/index.vue b/frontend/src/views/cronjob/cronjob/backup/index.vue similarity index 100% rename from frontend/src/views/cronjob/backup/index.vue rename to frontend/src/views/cronjob/cronjob/backup/index.vue diff --git a/frontend/src/views/cronjob/helper.ts b/frontend/src/views/cronjob/cronjob/helper.ts similarity index 100% rename from frontend/src/views/cronjob/helper.ts rename to frontend/src/views/cronjob/cronjob/helper.ts diff --git a/frontend/src/views/cronjob/cronjob/index.vue b/frontend/src/views/cronjob/cronjob/index.vue new file mode 100644 index 000000000000..6e494f47f93a --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/index.vue @@ -0,0 +1,391 @@ + + + diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue similarity index 100% rename from frontend/src/views/cronjob/operate/index.vue rename to frontend/src/views/cronjob/cronjob/operate/index.vue diff --git a/frontend/src/views/cronjob/record/index.vue b/frontend/src/views/cronjob/cronjob/record/index.vue similarity index 99% rename from frontend/src/views/cronjob/record/index.vue rename to frontend/src/views/cronjob/cronjob/record/index.vue index 883141545d0f..d11e8229399e 100644 --- a/frontend/src/views/cronjob/record/index.vue +++ b/frontend/src/views/cronjob/cronjob/record/index.vue @@ -229,7 +229,7 @@ import { MsgSuccess } from '@/utils/message'; import { listDbItems } from '@/api/modules/database'; import { listAppInstalled } from '@/api/modules/app'; import { shortcuts } from '@/utils/shortcuts'; -import TaskLog from '@/components/log/task/log-without-dialog.vue'; +import TaskLog from '@/components/log/task/index.vue'; const loading = ref(); const refresh = ref(false); diff --git a/frontend/src/views/cronjob/index.vue b/frontend/src/views/cronjob/index.vue index 59b4c3c74fb9..ef0b94d4ecd3 100644 --- a/frontend/src/views/cronjob/index.vue +++ b/frontend/src/views/cronjob/index.vue @@ -1,399 +1,23 @@ diff --git a/frontend/src/views/cronjob/library/index.vue b/frontend/src/views/cronjob/library/index.vue new file mode 100644 index 000000000000..a5b975d2d6cd --- /dev/null +++ b/frontend/src/views/cronjob/library/index.vue @@ -0,0 +1,225 @@ + + + diff --git a/frontend/src/views/cronjob/library/operate/index.vue b/frontend/src/views/cronjob/library/operate/index.vue new file mode 100644 index 000000000000..f0639a0a5a89 --- /dev/null +++ b/frontend/src/views/cronjob/library/operate/index.vue @@ -0,0 +1,126 @@ + + + diff --git a/frontend/src/views/cronjob/library/run/index.vue b/frontend/src/views/cronjob/library/run/index.vue new file mode 100644 index 000000000000..91cac9aee28b --- /dev/null +++ b/frontend/src/views/cronjob/library/run/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/frontend/src/views/terminal/command/index.vue b/frontend/src/views/terminal/command/index.vue index f7dccebc7771..51d166151e3e 100644 --- a/frontend/src/views/terminal/command/index.vue +++ b/frontend/src/views/terminal/command/index.vue @@ -18,7 +18,6 @@