diff --git a/agent/app/api/v2/backup.go b/agent/app/api/v2/backup.go index 27255f86d..3b2f1bf63 100644 --- a/agent/app/api/v2/backup.go +++ b/agent/app/api/v2/backup.go @@ -10,6 +10,21 @@ import ( "github.com/gin-gonic/gin" ) +func (b *BaseApi) CheckBackupUsed(c *gin.Context) { + id, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) + return + } + + if err := backupService.CheckUsed(id); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) + return + } + + helper.SuccessWithOutData(c) +} + // @Tags Backup Account // @Summary Page backup records // @Description 获取备份记录列表分页 diff --git a/agent/app/api/v2/entry.go b/agent/app/api/v2/entry.go index 294f46cf0..972ca2ca8 100644 --- a/agent/app/api/v2/entry.go +++ b/agent/app/api/v2/entry.go @@ -30,8 +30,6 @@ var ( cronjobService = service.NewICronjobService() - hostService = service.NewIHostService() - groupService = service.NewIGroupService() fileService = service.NewIFileService() sshService = service.NewISSHService() firewallService = service.NewIFirewallService() @@ -45,8 +43,6 @@ var ( settingService = service.NewISettingService() backupService = service.NewIBackupService() - commandService = service.NewICommandService() - websiteService = service.NewIWebsiteService() websiteDnsAccountService = service.NewIWebsiteDnsAccountService() websiteSSLService = service.NewIWebsiteSSLService() diff --git a/agent/app/api/v2/terminal.go b/agent/app/api/v2/terminal.go index 5a461e1fe..b85ff956c 100644 --- a/agent/app/api/v2/terminal.go +++ b/agent/app/api/v2/terminal.go @@ -12,67 +12,12 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" - "github.com/1Panel-dev/1Panel/agent/utils/copier" - "github.com/1Panel-dev/1Panel/agent/utils/ssh" "github.com/1Panel-dev/1Panel/agent/utils/terminal" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/pkg/errors" ) -func (b *BaseApi) WsSsh(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() - - id, err := strconv.Atoi(c.Query("id")) - if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) { - 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 - } - host, err := hostService.GetHostInfo(uint(id)) - if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) { - return - } - var connInfo ssh.ConnInfo - _ = copier.Copy(&connInfo, &host) - connInfo.PrivateKey = []byte(host.PrivateKey) - if len(host.PassPhrase) != 0 { - connInfo.PassPhrase = []byte(host.PassPhrase) - } - - client, err := connInfo.NewClient() - 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, true, connInfo.Client, wsConn) - if wshandleError(wsConn, err) { - return - } - defer sws.Close() - - quitChan := make(chan bool, 3) - sws.Start(quitChan) - go sws.Wait(quitChan) - - <-quitChan - - if wshandleError(wsConn, err) { - return - } -} - func (b *BaseApi) RedisWsSsh(c *gin.Context) { wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if err != nil { diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go index 035cdc38b..084c00197 100644 --- a/agent/app/api/v2/website.go +++ b/agent/app/api/v2/website.go @@ -957,3 +957,15 @@ func (b *BaseApi) UpdateLoadBalanceFile(c *gin.Context) { } helper.SuccessWithOutData(c) } + +func (b *BaseApi) ChangeWebsiteGroup(c *gin.Context) { + var req dto.UpdateGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ChangeGroup(req.Group, req.NewGroup); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/agent/app/dto/common_req.go b/agent/app/dto/common_req.go index b493fac55..a3e68ef34 100644 --- a/agent/app/dto/common_req.go +++ b/agent/app/dto/common_req.go @@ -52,3 +52,8 @@ type OperationWithNameAndType struct { Name string `json:"name"` Type string `json:"type" validate:"required"` } + +type UpdateGroup struct { + Group uint `json:"group"` + NewGroup uint `json:"newGroup"` +} diff --git a/agent/app/dto/monitor.go b/agent/app/dto/monitor.go index 780b1c790..fa9baf56d 100644 --- a/agent/app/dto/monitor.go +++ b/agent/app/dto/monitor.go @@ -14,3 +14,15 @@ type MonitorData struct { Date []time.Time `json:"date"` Value []interface{} `json:"value"` } + +type MonitorSetting struct { + MonitorStatus string `json:"monitorStatus"` + MonitorStoreDays string `json:"monitorStoreDays"` + MonitorInterval string `json:"monitorInterval"` + DefaultNetwork string `json:"defaultNetwork"` +} + +type MonitorSettingUpdate struct { + Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork"` + Value string `json:"value"` +} diff --git a/agent/app/model/command.go b/agent/app/model/command.go deleted file mode 100644 index 2e72e714d..000000000 --- a/agent/app/model/command.go +++ /dev/null @@ -1,14 +0,0 @@ -package model - -type Command struct { - BaseModel - Name string `gorm:"unique;not null" json:"name"` - GroupID uint `json:"groupID"` - Command string `gorm:"not null" json:"command"` -} - -type RedisCommand struct { - BaseModel - Name string `gorm:"unique;not null" json:"name"` - Command string `gorm:"not null" json:"command"` -} diff --git a/agent/app/model/group.go b/agent/app/model/group.go deleted file mode 100644 index 0a6758531..000000000 --- a/agent/app/model/group.go +++ /dev/null @@ -1,8 +0,0 @@ -package model - -type Group struct { - BaseModel - IsDefault bool `json:"isDefault"` - Name string `gorm:"not null" json:"name"` - Type string `gorm:"not null" json:"type"` -} diff --git a/agent/app/repo/cronjob.go b/agent/app/repo/cronjob.go index d59f4a7fc..f6e9acf2d 100644 --- a/agent/app/repo/cronjob.go +++ b/agent/app/repo/cronjob.go @@ -21,7 +21,7 @@ type ICronjobRepo interface { Create(cronjob *model.Cronjob) error WithByJobID(id int) DBOption WithByDbName(name string) DBOption - WithByDefaultDownload(account string) DBOption + WithByDownloadAccountID(id uint) DBOption WithByRecordDropID(id int) DBOption WithByRecordFile(file string) DBOption Save(id uint, cronjob model.Cronjob) error @@ -124,9 +124,9 @@ func (c *CronjobRepo) WithByDbName(name string) DBOption { } } -func (c *CronjobRepo) WithByDefaultDownload(account string) DBOption { +func (c *CronjobRepo) WithByDownloadAccountID(id uint) DBOption { return func(g *gorm.DB) *gorm.DB { - return g.Where("default_download = ?", account) + return g.Where("download_account_id = ?", id) } } diff --git a/agent/app/repo/ftp.go b/agent/app/repo/ftp.go index 8861b25ee..35e071efe 100644 --- a/agent/app/repo/ftp.go +++ b/agent/app/repo/ftp.go @@ -15,6 +15,7 @@ type IFtpRepo interface { Create(ftp *model.Ftp) error Update(id uint, vars map[string]interface{}) error Delete(opts ...DBOption) error + WithLikeUser(user string) DBOption WithByUser(user string) DBOption } @@ -33,6 +34,12 @@ func (u *FtpRepo) Get(opts ...DBOption) (model.Ftp, error) { } func (h *FtpRepo) WithByUser(user string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("user = ?", user) + } +} + +func (h *FtpRepo) WithLikeUser(user string) DBOption { return func(g *gorm.DB) *gorm.DB { if len(user) == 0 { return g diff --git a/agent/app/repo/host.go b/agent/app/repo/host.go index eac052ab7..05081136a 100644 --- a/agent/app/repo/host.go +++ b/agent/app/repo/host.go @@ -3,23 +3,11 @@ package repo import ( "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/global" - "gorm.io/gorm" ) type HostRepo struct{} type IHostRepo interface { - Get(opts ...DBOption) (model.Host, error) - GetList(opts ...DBOption) ([]model.Host, error) - Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error) - WithByInfo(info string) DBOption - WithByPort(port uint) DBOption - WithByUser(user string) DBOption - WithByAddr(addr string) DBOption - Create(host *model.Host) error - Update(id uint, vars map[string]interface{}) error - Delete(opts ...DBOption) error - GetFirewallRecord(opts ...DBOption) (model.Firewall, error) ListFirewallRecord() ([]model.Firewall, error) SaveFirewallRecord(firewall *model.Firewall) error @@ -31,88 +19,6 @@ func NewIHostRepo() IHostRepo { return &HostRepo{} } -func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) { - var host model.Host - db := global.DB - for _, opt := range opts { - db = opt(db) - } - err := db.First(&host).Error - return host, err -} - -func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { - var hosts []model.Host - db := global.DB.Model(&model.Host{}) - for _, opt := range opts { - db = opt(db) - } - err := db.Find(&hosts).Error - return hosts, err -} - -func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) { - var users []model.Host - db := global.DB.Model(&model.Host{}) - 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 (h *HostRepo) WithByInfo(info string) DBOption { - return func(g *gorm.DB) *gorm.DB { - if len(info) == 0 { - return g - } - infoStr := "%" + info + "%" - return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr) - } -} - -func (h *HostRepo) WithByPort(port uint) DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("port = ?", port) - } -} -func (h *HostRepo) WithByUser(user string) DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("user = ?", user) - } -} -func (h *HostRepo) WithByAddr(addr string) DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("addr = ?", addr) - } -} -func (h *HostRepo) WithByGroup(group string) DBOption { - return func(g *gorm.DB) *gorm.DB { - if len(group) == 0 { - return g - } - return g.Where("group_belong = ?", group) - } -} - -func (h *HostRepo) Create(host *model.Host) error { - return global.DB.Create(host).Error -} - -func (h *HostRepo) Update(id uint, vars map[string]interface{}) error { - return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error -} - -func (h *HostRepo) Delete(opts ...DBOption) error { - db := global.DB - for _, opt := range opts { - db = opt(db) - } - return db.Delete(&model.Host{}).Error -} - func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) { var firewall model.Firewall db := global.DB diff --git a/agent/app/repo/website.go b/agent/app/repo/website.go index d6bc2c47f..98c4417ae 100644 --- a/agent/app/repo/website.go +++ b/agent/app/repo/website.go @@ -31,6 +31,8 @@ type IWebsiteRepo interface { DeleteBy(ctx context.Context, opts ...DBOption) error Create(ctx context.Context, app *model.Website) error DeleteAll(ctx context.Context) error + + UpdateGroup(group, newGroup uint) error } func NewIWebsiteRepo() IWebsiteRepo { @@ -158,3 +160,7 @@ func (w *WebsiteRepo) DeleteBy(ctx context.Context, opts ...DBOption) error { func (w *WebsiteRepo) DeleteAll(ctx context.Context) error { return getTx(ctx).Where("1 = 1 ").Delete(&model.Website{}).Error } + +func (w *WebsiteRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Website{}).Where("website_group_id = ?", group).Updates(map[string]interface{}{"website_group_id": newGroup}).Error +} diff --git a/agent/app/service/backup.go b/agent/app/service/backup.go index d89368542..4577a6c71 100644 --- a/agent/app/service/backup.go +++ b/agent/app/service/backup.go @@ -3,17 +3,20 @@ package service import ( "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "net/http" "os" "path" "sort" + "strconv" "strings" "sync" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage" @@ -26,6 +29,8 @@ import ( type BackupService struct{} type IBackupService interface { + CheckUsed(id uint) error + SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) DownloadRecord(info dto.DownloadRecord) (string, error) @@ -97,6 +102,22 @@ func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchBy return total, datas, err } +func (u *BackupService) CheckUsed(id uint) error { + cronjobs, _ := cronjobRepo.List() + for _, job := range cronjobs { + if job.DownloadAccountID == id { + return buserr.New(constant.ErrBackupInUsed) + } + ids := strings.Split(job.SourceAccountIDs, ",") + for _, idItem := range ids { + if idItem == fmt.Sprintf("%v", id) { + return buserr.New(constant.ErrBackupInUsed) + } + } + } + return nil +} + type loadSizeHelper struct { isOk bool backupPath string @@ -309,7 +330,15 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) { accounts[i].Credential, _ = encrypt.StringDecryptWithKey(accounts[i].Credential, setting.Value) } } else { - bodyItem, err := json.Marshal(ids) + var idItems []uint + for i := 0; i < len(ids); i++ { + item, _ := strconv.Atoi(ids[i]) + idItems = append(idItems, uint(item)) + } + operateByIDs := struct { + IDs []uint `json:"ids"` + }{IDs: idItems} + bodyItem, err := json.Marshal(operateByIDs) if err != nil { return nil, err } @@ -327,6 +356,18 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) { } clientMap := make(map[string]backupClientHelper) for _, item := range accounts { + if !global.IsMaster { + accessItem, err := base64.StdEncoding.DecodeString(item.AccessKey) + if err != nil { + return nil, err + } + item.AccessKey = string(accessItem) + secretItem, err := base64.StdEncoding.DecodeString(item.Credential) + if err != nil { + return nil, err + } + item.Credential = string(secretItem) + } backClient, err := newClient(&item) if err != nil { return nil, err diff --git a/agent/app/service/command.go b/agent/app/service/command.go deleted file mode 100644 index 9cbca0777..000000000 --- a/agent/app/service/command.go +++ /dev/null @@ -1,183 +0,0 @@ -package service - -import ( - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/1Panel-dev/1Panel/agent/constant" - "github.com/jinzhu/copier" - "github.com/pkg/errors" -) - -type CommandService struct{} - -type ICommandService interface { - List() ([]dto.CommandInfo, error) - SearchForTree() ([]dto.CommandTree, error) - SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) - Create(commandDto dto.CommandOperate) error - Update(id uint, upMap map[string]interface{}) error - Delete(ids []uint) error - - SearchRedisCommandWithPage(search dto.SearchWithPage) (int64, interface{}, error) - ListRedisCommand() ([]dto.RedisCommand, error) - SaveRedisCommand(commandDto dto.RedisCommand) error - DeleteRedisCommand(ids []uint) error -} - -func NewICommandService() ICommandService { - return &CommandService{} -} - -func (u *CommandService) List() ([]dto.CommandInfo, error) { - commands, err := commandRepo.GetList(commonRepo.WithOrderBy("name")) - if err != nil { - return nil, constant.ErrRecordNotFound - } - var dtoCommands []dto.CommandInfo - for _, command := range commands { - var item dto.CommandInfo - if err := copier.Copy(&item, &command); err != nil { - return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - dtoCommands = append(dtoCommands, item) - } - return dtoCommands, err -} - -func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) { - cmdList, err := commandRepo.GetList(commonRepo.WithOrderBy("name")) - if err != nil { - return nil, err - } - groups, err := groupRepo.GetList(commonRepo.WithByType("command")) - if err != nil { - return nil, err - } - var lists []dto.CommandTree - for _, group := range groups { - var data dto.CommandTree - data.ID = group.ID + 10000 - data.Label = group.Name - for _, cmd := range cmdList { - if cmd.GroupID == group.ID { - data.Children = append(data.Children, dto.CommandInfo{ID: cmd.ID, Name: cmd.Name, Command: cmd.Command}) - } - } - if len(data.Children) != 0 { - lists = append(lists, data) - } - } - return lists, err -} - -func (u *CommandService) SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) { - total, commands, err := commandRepo.Page(search.Page, search.PageSize, commandRepo.WithLikeName(search.Name), commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order)) - if err != nil { - return 0, nil, err - } - groups, _ := groupRepo.GetList(commonRepo.WithByType("command"), commonRepo.WithOrderBy("name")) - var dtoCommands []dto.CommandInfo - for _, command := range commands { - var item dto.CommandInfo - if err := copier.Copy(&item, &command); err != nil { - return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - for _, group := range groups { - if command.GroupID == group.ID { - item.GroupBelong = group.Name - item.GroupID = group.ID - } - } - dtoCommands = append(dtoCommands, item) - } - return total, dtoCommands, err -} - -func (u *CommandService) Create(commandDto dto.CommandOperate) error { - command, _ := commandRepo.Get(commonRepo.WithByName(commandDto.Name)) - if command.ID != 0 { - return constant.ErrRecordExist - } - if err := copier.Copy(&command, &commandDto); err != nil { - return errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - if err := commandRepo.Create(&command); err != nil { - return err - } - return nil -} - -func (u *CommandService) Delete(ids []uint) error { - if len(ids) == 1 { - command, _ := commandRepo.Get(commonRepo.WithByID(ids[0])) - if command.ID == 0 { - return constant.ErrRecordNotFound - } - return commandRepo.Delete(commonRepo.WithByID(ids[0])) - } - return commandRepo.Delete(commonRepo.WithIdsIn(ids)) -} - -func (u *CommandService) Update(id uint, upMap map[string]interface{}) error { - return commandRepo.Update(id, upMap) -} - -func (u *CommandService) SearchRedisCommandWithPage(search dto.SearchWithPage) (int64, interface{}, error) { - total, commands, err := commandRepo.PageRedis(search.Page, search.PageSize, commandRepo.WithLikeName(search.Info)) - if err != nil { - return 0, nil, err - } - var dtoCommands []dto.RedisCommand - for _, command := range commands { - var item dto.RedisCommand - if err := copier.Copy(&item, &command); err != nil { - return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - dtoCommands = append(dtoCommands, item) - } - return total, dtoCommands, err -} - -func (u *CommandService) ListRedisCommand() ([]dto.RedisCommand, error) { - commands, err := commandRepo.GetRedisList() - if err != nil { - return nil, constant.ErrRecordNotFound - } - var dtoCommands []dto.RedisCommand - for _, command := range commands { - var item dto.RedisCommand - if err := copier.Copy(&item, &command); err != nil { - return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - dtoCommands = append(dtoCommands, item) - } - return dtoCommands, err -} - -func (u *CommandService) SaveRedisCommand(req dto.RedisCommand) error { - if req.ID == 0 { - command, _ := commandRepo.GetRedis(commonRepo.WithByName(req.Name)) - if command.ID != 0 { - return constant.ErrRecordExist - } - } - var command model.RedisCommand - if err := copier.Copy(&command, &req); err != nil { - return errors.WithMessage(constant.ErrStructTransform, err.Error()) - } - if err := commandRepo.SaveRedis(&command); err != nil { - return err - } - return nil -} - -func (u *CommandService) DeleteRedisCommand(ids []uint) error { - if len(ids) == 1 { - command, _ := commandRepo.GetRedis(commonRepo.WithByID(ids[0])) - if command.ID == 0 { - return constant.ErrRecordNotFound - } - return commandRepo.DeleteRedis(commonRepo.WithByID(ids[0])) - } - return commandRepo.DeleteRedis(commonRepo.WithIdsIn(ids)) -} diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go index 8864347fa..01976eaae 100644 --- a/agent/app/service/entry.go +++ b/agent/app/service/entry.go @@ -22,8 +22,6 @@ var ( cronjobRepo = repo.NewICronjobRepo() hostRepo = repo.NewIHostRepo() - groupRepo = repo.NewIGroupRepo() - commandRepo = repo.NewICommandRepo() ftpRepo = repo.NewIFtpRepo() clamRepo = repo.NewIClamRepo() monitorRepo = repo.NewIMonitorRepo() diff --git a/agent/app/service/ftp.go b/agent/app/service/ftp.go index d0dfef158..a7440873c 100644 --- a/agent/app/service/ftp.go +++ b/agent/app/service/ftp.go @@ -74,7 +74,7 @@ func (u *FtpService) Operate(operation string) error { } func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) { - total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithByUser(req.Info), commonRepo.WithOrderBy("created_at desc")) + total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), commonRepo.WithOrderBy("created_at desc")) if err != nil { return 0, nil, err } @@ -142,7 +142,7 @@ func (f *FtpService) Create(req dto.FtpCreate) (uint, error) { if err != nil { return 0, err } - userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User)) + userInDB, _ := ftpRepo.Get(ftpRepo.WithByUser(req.User)) if userInDB.ID != 0 { return 0, constant.ErrRecordExist } diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index 46e911119..b9a887047 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -181,10 +181,6 @@ func (u *SSHService) Update(req dto.SSHUpdate) error { if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil { global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err) } - - if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil { - global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err) - } } _, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName) diff --git a/agent/app/service/website.go b/agent/app/service/website.go index c82162354..68e4dfeb8 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -10,7 +10,6 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/1Panel-dev/1Panel/agent/app/task" "os" "path" "reflect" @@ -20,6 +19,8 @@ import ( "syscall" "time" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/jinzhu/copier" @@ -114,6 +115,8 @@ type IWebsiteService interface { DeleteLoadBalance(req request.WebsiteLBDelete) error UpdateLoadBalance(req request.WebsiteLBUpdate) error UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error + + ChangeGroup(group, newGroup uint) error } func NewIWebsiteService() IWebsiteService { @@ -3193,3 +3196,7 @@ func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) e }() return opNginx(nginxInstall.ContainerName, constant.NginxReload) } + +func (w WebsiteService) ChangeGroup(group, newGroup uint) error { + return websiteRepo.UpdateGroup(group, newGroup) +} diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index c28190410..2f6567f67 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -11,10 +11,8 @@ func Init() { m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{ migrations.AddTable, migrations.AddMonitorTable, - migrations.InitHost, migrations.InitSetting, migrations.InitImageRepo, - migrations.InitDefaultGroup, migrations.InitDefaultCA, migrations.InitPHPExtensions, migrations.AddTask, diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 2c4ea07d0..e17ff08f8 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -28,7 +28,6 @@ var AddTable = &gormigrate.Migration{ &model.App{}, &model.BackupRecord{}, &model.Clam{}, - &model.Command{}, &model.ComposeTemplate{}, &model.Compose{}, &model.Cronjob{}, @@ -39,15 +38,12 @@ var AddTable = &gormigrate.Migration{ &model.Forward{}, &model.Firewall{}, &model.Ftp{}, - &model.Group{}, - &model.Host{}, &model.ImageRepo{}, &model.JobRecords{}, &model.MonitorBase{}, &model.MonitorIO{}, &model.MonitorNetwork{}, &model.PHPExtensions{}, - &model.RedisCommand{}, &model.Runtime{}, &model.Setting{}, &model.Snapshot{}, @@ -74,25 +70,6 @@ var AddMonitorTable = &gormigrate.Migration{ }, } -var InitHost = &gormigrate.Migration{ - ID: "20240722-init-host", - Migrate: func(tx *gorm.DB) error { - group := model.Group{ - Name: "default", Type: "host", IsDefault: true, - } - if err := tx.Create(&group).Error; err != nil { - return err - } - host := model.Host{ - Name: "localhost", Addr: "127.0.0.1", User: "root", Port: 22, AuthMode: "password", GroupID: group.ID, - } - if err := tx.Create(&host).Error; err != nil { - return err - } - return nil - }, -} - var InitSetting = &gormigrate.Migration{ ID: "20240722-init-setting", Migrate: func(tx *gorm.DB) error { @@ -228,25 +205,6 @@ var InitImageRepo = &gormigrate.Migration{ }, } -var InitDefaultGroup = &gormigrate.Migration{ - ID: "20240722-init-default-group", - Migrate: func(tx *gorm.DB) error { - websiteGroup := &model.Group{ - Name: "默认", - IsDefault: true, - Type: "website", - } - if err := tx.Create(websiteGroup).Error; err != nil { - return err - } - commandGroup := &model.Group{IsDefault: true, Name: "默认", Type: "command"} - if err := tx.Create(commandGroup).Error; err != nil { - return err - } - return nil - }, -} - var InitDefaultCA = &gormigrate.Migration{ ID: "20240722-init-default-ca", Migrate: func(tx *gorm.DB) error { diff --git a/agent/router/backup.go b/agent/router/backup.go new file mode 100644 index 000000000..c951f3380 --- /dev/null +++ b/agent/router/backup.go @@ -0,0 +1,24 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type BackupRouter struct{} + +func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) { + backupRouter := Router.Group("backups") + baseApi := v2.ApiGroupApp.BaseApi + { + backupRouter.GET("/check/:id", baseApi.CheckBackupUsed) + backupRouter.POST("/backup", baseApi.Backup) + backupRouter.POST("/recover", baseApi.Recover) + backupRouter.POST("/recover/byupload", baseApi.RecoverByUpload) + backupRouter.POST("/search/files", baseApi.LoadFilesFromBackup) + backupRouter.POST("/record/search", baseApi.SearchBackupRecords) + backupRouter.POST("/record/search/bycronjob", baseApi.SearchBackupRecordsByCronjob) + backupRouter.POST("/record/download", baseApi.DownloadRecord) + backupRouter.POST("/record/del", baseApi.DeleteBackupRecord) + } +} diff --git a/agent/router/common.go b/agent/router/common.go index b96153767..f54a07ddc 100644 --- a/agent/router/common.go +++ b/agent/router/common.go @@ -8,12 +8,11 @@ func commonGroups() []CommonRouter { &LogRouter{}, &FileRouter{}, &ToolboxRouter{}, - &TerminalRouter{}, &CronjobRouter{}, + &BackupRouter{}, &SettingRouter{}, &AppRouter{}, &WebsiteRouter{}, - &WebsiteGroupRouter{}, &WebsiteDnsAccountRouter{}, &WebsiteAcmeAccountRouter{}, &WebsiteSSLRouter{}, diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go index 15990d97a..4e599c716 100644 --- a/agent/router/ro_host.go +++ b/agent/router/ro_host.go @@ -11,15 +11,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { hostRouter := Router.Group("hosts") baseApi := v2.ApiGroupApp.BaseApi { - hostRouter.POST("", baseApi.CreateHost) - hostRouter.POST("/del", baseApi.DeleteHost) - hostRouter.POST("/update", baseApi.UpdateHost) - hostRouter.POST("/update/group", baseApi.UpdateHostGroup) - hostRouter.POST("/search", baseApi.SearchHost) - hostRouter.POST("/tree", baseApi.HostTree) - hostRouter.POST("/test/byinfo", baseApi.TestByInfo) - hostRouter.POST("/test/byid/:id", baseApi.TestByID) - hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo) hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule) hostRouter.POST("/firewall/operate", baseApi.OperateFirewall) @@ -47,18 +38,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.POST("/ssh/operate", baseApi.OperateSSH) - hostRouter.GET("/command", baseApi.ListCommand) - hostRouter.POST("/command", baseApi.CreateCommand) - hostRouter.POST("/command/del", baseApi.DeleteCommand) - hostRouter.POST("/command/search", baseApi.SearchCommand) - hostRouter.GET("/command/tree", baseApi.SearchCommandTree) - hostRouter.POST("/command/update", baseApi.UpdateCommand) - - hostRouter.GET("/command/redis", baseApi.ListRedisCommand) - hostRouter.POST("/command/redis", baseApi.SaveRedisCommand) - hostRouter.POST("/command/redis/search", baseApi.SearchRedisCommand) - hostRouter.POST("/command/redis/del", baseApi.DeleteRedisCommand) - hostRouter.POST("/tool", baseApi.GetToolStatus) hostRouter.POST("/tool/init", baseApi.InitToolConfig) hostRouter.POST("/tool/operate", baseApi.OperateTool) diff --git a/agent/router/ro_terminal.go b/agent/router/ro_terminal.go deleted file mode 100644 index ec17a3146..000000000 --- a/agent/router/ro_terminal.go +++ /dev/null @@ -1,16 +0,0 @@ -package router - -import ( - v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" - "github.com/gin-gonic/gin" -) - -type TerminalRouter struct{} - -func (s *TerminalRouter) InitRouter(Router *gin.RouterGroup) { - terminalRouter := Router.Group("terminals") - baseApi := v2.ApiGroupApp.BaseApi - { - terminalRouter.GET("", baseApi.WsSsh) - } -} diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go index 35050c89f..ff7b5af6c 100644 --- a/agent/router/ro_website.go +++ b/agent/router/ro_website.go @@ -24,6 +24,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) { websiteRouter.GET("/:id", baseApi.GetWebsite) websiteRouter.POST("/del", baseApi.DeleteWebsite) websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer) + websiteRouter.POST("/group/change", baseApi.ChangeWebsiteGroup) websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains) websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain) diff --git a/agent/app/api/v2/command.go b/core/app/api/v2/command.go similarity index 53% rename from agent/app/api/v2/command.go rename to core/app/api/v2/command.go index b9cb437b1..54c1b848e 100644 --- a/agent/app/api/v2/command.go +++ b/core/app/api/v2/command.go @@ -1,9 +1,9 @@ package v2 import ( - "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/constant" "github.com/gin-gonic/gin" ) @@ -14,7 +14,7 @@ import ( // @Param request body dto.CommandOperate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/command [post] +// @Router /core/commands [post] // @x-panel-log {"bodyKeys":["name","command"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建快捷命令 [name][command]","formatEN":"create quick command [name][command]"} func (b *BaseApi) CreateCommand(c *gin.Context) { var req dto.CommandOperate @@ -29,28 +29,6 @@ func (b *BaseApi) CreateCommand(c *gin.Context) { helper.SuccessWithData(c, nil) } -// @Tags Redis Command -// @Summary Save redis command -// @Description 保存 Redis 快速命令 -// @Accept json -// @Param request body dto.RedisCommand true "request" -// @Success 200 -// @Security ApiKeyAuth -// @Router /hosts/command/redis [post] -// @x-panel-log {"bodyKeys":["name","command"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"保存 redis 快捷命令 [name][command]","formatEN":"save quick command for redis [name][command]"} -func (b *BaseApi) SaveRedisCommand(c *gin.Context) { - var req dto.RedisCommand - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - if err := commandService.SaveRedisCommand(req); err != nil { - helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) - return - } - helper.SuccessWithData(c, nil) -} - // @Tags Command // @Summary Page commands // @Description 获取快速命令列表分页 @@ -58,7 +36,7 @@ func (b *BaseApi) SaveRedisCommand(c *gin.Context) { // @Param request body dto.SearchWithPage true "request" // @Success 200 {object} dto.PageResult // @Security ApiKeyAuth -// @Router /hosts/command/search [post] +// @Router /core/commands/search [post] func (b *BaseApi) SearchCommand(c *gin.Context) { var req dto.SearchCommandWithPage if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -77,57 +55,21 @@ func (b *BaseApi) SearchCommand(c *gin.Context) { }) } -// @Tags Redis Command -// @Summary Page redis commands -// @Description 获取 redis 快速命令列表分页 -// @Accept json -// @Param request body dto.SearchWithPage true "request" -// @Success 200 {object} dto.PageResult -// @Security ApiKeyAuth -// @Router /hosts/command/redis/search [post] -func (b *BaseApi) SearchRedisCommand(c *gin.Context) { - var req dto.SearchWithPage - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - total, list, err := commandService.SearchRedisCommandWithPage(req) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) - return - } - - helper.SuccessWithData(c, dto.PageResult{ - Items: list, - Total: total, - }) -} - // @Tags Command // @Summary Tree commands // @Description 获取快速命令树 // @Accept json +// @Param request body dto.OperateByType true "request" // @Success 200 {Array} dto.CommandTree // @Security ApiKeyAuth -// @Router /hosts/command/tree [get] +// @Router /core/commands/tree [get] func (b *BaseApi) SearchCommandTree(c *gin.Context) { - list, err := commandService.SearchForTree() - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - helper.SuccessWithData(c, list) -} - -// @Tags Redis Command -// @Summary List redis commands -// @Description 获取 redis 快速命令列表 -// @Success 200 {Array} dto.RedisCommand -// @Security ApiKeyAuth -// @Router /hosts/command/redis [get] -func (b *BaseApi) ListRedisCommand(c *gin.Context) { - list, err := commandService.ListRedisCommand() + list, err := commandService.SearchForTree(req) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return @@ -139,11 +81,18 @@ func (b *BaseApi) ListRedisCommand(c *gin.Context) { // @Tags Command // @Summary List commands // @Description 获取快速命令列表 +// @Accept json +// @Param request body dto.OperateByType true "request" // @Success 200 {object} dto.CommandInfo // @Security ApiKeyAuth -// @Router /hosts/command [get] +// @Router /core/commands/command [get] func (b *BaseApi) ListCommand(c *gin.Context) { - list, err := commandService.List() + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := commandService.List(req) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return @@ -156,40 +105,18 @@ func (b *BaseApi) ListCommand(c *gin.Context) { // @Summary Delete command // @Description 删除快速命令 // @Accept json -// @Param request body dto.BatchDeleteReq true "request" +// @Param request body dto.OperateByIDs true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/command/del [post] +// @Router /core/commands/del [post] // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"commands","output_column":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"} func (b *BaseApi) DeleteCommand(c *gin.Context) { - var req dto.BatchDeleteReq + var req dto.OperateByIDs if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if err := commandService.Delete(req.Ids); err != nil { - helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) - return - } - helper.SuccessWithData(c, nil) -} - -// @Tags Redis Command -// @Summary Delete redis command -// @Description 删除 redis 快速命令 -// @Accept json -// @Param request body dto.BatchDeleteReq true "request" -// @Success 200 -// @Security ApiKeyAuth -// @Router /hosts/command/redis/del [post] -// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"redis_commands","output_column":"name","output_value":"names"}],"formatZH":"删除 redis 快捷命令 [names]","formatEN":"delete quick command of redis [names]"} -func (b *BaseApi) DeleteRedisCommand(c *gin.Context) { - var req dto.BatchDeleteReq - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - if err := commandService.DeleteRedisCommand(req.Ids); err != nil { + if err := commandService.Delete(req.IDs); err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } @@ -203,7 +130,7 @@ func (b *BaseApi) DeleteRedisCommand(c *gin.Context) { // @Param request body dto.CommandOperate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/command/update [post] +// @Router /core/commands/update [post] // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新快捷命令 [name]","formatEN":"update quick command [name]"} func (b *BaseApi) UpdateCommand(c *gin.Context) { var req dto.CommandOperate diff --git a/core/app/api/v2/entry.go b/core/app/api/v2/entry.go index b83ba1774..196897634 100644 --- a/core/app/api/v2/entry.go +++ b/core/app/api/v2/entry.go @@ -9,9 +9,12 @@ type ApiGroup struct { var ApiGroupApp = new(ApiGroup) var ( + hostService = service.NewIHostService() authService = service.NewIAuthService() backupService = service.NewIBackupService() settingService = service.NewISettingService() logService = service.NewILogService() upgradeService = service.NewIUpgradeService() + groupService = service.NewIGroupService() + commandService = service.NewICommandService() ) diff --git a/agent/app/api/v2/group.go b/core/app/api/v2/group.go similarity index 88% rename from agent/app/api/v2/group.go rename to core/app/api/v2/group.go index 233a814fa..ef6a56f4f 100644 --- a/agent/app/api/v2/group.go +++ b/core/app/api/v2/group.go @@ -1,9 +1,9 @@ package v2 import ( - "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/constant" "github.com/gin-gonic/gin" ) @@ -14,7 +14,7 @@ import ( // @Param request body dto.GroupCreate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /groups [post] +// @Router /core/groups [post] // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"} func (b *BaseApi) CreateGroup(c *gin.Context) { var req dto.GroupCreate @@ -36,7 +36,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) { // @Param request body dto.OperateByID true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /groups/del [post] +// @Router /core/groups/del [post] // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"} func (b *BaseApi) DeleteGroup(c *gin.Context) { var req dto.OperateByID @@ -58,7 +58,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) { // @Param request body dto.GroupUpdate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /groups/update [post] +// @Router /core/groups/update [post] // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"} func (b *BaseApi) UpdateGroup(c *gin.Context) { var req dto.GroupUpdate @@ -78,11 +78,11 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) { // @Description 查询系统组 // @Accept json // @Param request body dto.GroupSearch true "request" -// @Success 200 {array} dto.GroupInfo +// @Success 200 {array} dto.OperateByType // @Security ApiKeyAuth -// @Router /groups/search [post] +// @Router /core/groups/search [post] func (b *BaseApi) ListGroup(c *gin.Context) { - var req dto.GroupSearch + var req dto.OperateByType if err := helper.CheckBindAndValidate(&req, c); err != nil { return } diff --git a/agent/app/api/v2/host.go b/core/app/api/v2/host.go similarity index 64% rename from agent/app/api/v2/host.go rename to core/app/api/v2/host.go index 957fb7fc4..0d4c0bd0a 100644 --- a/agent/app/api/v2/host.go +++ b/core/app/api/v2/host.go @@ -1,11 +1,23 @@ package v2 import ( - "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/constant" - "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "encoding/base64" + "encoding/json" + "net/http" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/copier" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/ssh" + "github.com/1Panel-dev/1Panel/core/utils/terminal" "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pkg/errors" ) // @Tags Host @@ -15,7 +27,7 @@ import ( // @Param request body dto.HostOperate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts [post] +// @Router /core/hosts [post] // @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"} func (b *BaseApi) CreateHost(c *gin.Context) { var req dto.HostOperate @@ -38,7 +50,7 @@ func (b *BaseApi) CreateHost(c *gin.Context) { // @Param request body dto.HostConnTest true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/test/byinfo [post] +// @Router /core/hosts/test/byinfo [post] func (b *BaseApi) TestByInfo(c *gin.Context) { var req dto.HostConnTest if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -56,15 +68,20 @@ func (b *BaseApi) TestByInfo(c *gin.Context) { // @Param id path integer true "request" // @Success 200 {boolean} connStatus // @Security ApiKeyAuth -// @Router /hosts/test/byid/:id [post] +// @Router /core/hosts/test/byid/:id [post] func (b *BaseApi) TestByID(c *gin.Context) { - id, err := helper.GetParamID(c) + idParam, ok := c.Params.Get("id") + if !ok { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("no such params find in request")) + return + } + intNum, err := strconv.Atoi(idParam) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - connStatus := hostService.TestLocalConn(id) + connStatus := hostService.TestLocalConn(uint(intNum)) helper.SuccessWithData(c, connStatus) } @@ -75,10 +92,10 @@ func (b *BaseApi) TestByID(c *gin.Context) { // @Param request body dto.SearchForTree true "request" // @Success 200 {array} dto.HostTree // @Security ApiKeyAuth -// @Router /hosts/tree [post] +// @Router /core/hosts/tree [post] func (b *BaseApi) HostTree(c *gin.Context) { var req dto.SearchForTree - if err := helper.CheckBind(&req, c); err != nil { + if err := helper.CheckBindAndValidate(&req, c); err != nil { return } @@ -98,7 +115,7 @@ func (b *BaseApi) HostTree(c *gin.Context) { // @Param request body dto.SearchHostWithPage true "request" // @Success 200 {array} dto.HostTree // @Security ApiKeyAuth -// @Router /hosts/search [post] +// @Router /core/hosts/search [post] func (b *BaseApi) SearchHost(c *gin.Context) { var req dto.SearchHostWithPage if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -124,15 +141,15 @@ func (b *BaseApi) SearchHost(c *gin.Context) { // @Param request body dto.BatchDeleteReq true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/del [post] +// @Router /core/hosts/del [post] // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"} func (b *BaseApi) DeleteHost(c *gin.Context) { - var req dto.BatchDeleteReq + var req dto.OperateByIDs if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if err := hostService.Delete(req.Ids); err != nil { + if err := hostService.Delete(req.IDs); err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } @@ -146,7 +163,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) { // @Param request body dto.HostOperate true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/update [post] +// @Router /core/hosts/update [post] // @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机信息 [name][addr]","formatEN":"update host [name][addr]"} func (b *BaseApi) UpdateHost(c *gin.Context) { var req dto.HostOperate @@ -212,7 +229,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { // @Param request body dto.ChangeHostGroup true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /hosts/update/group [post] +// @Router /core/hosts/update/group [post] // @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"} func (b *BaseApi) UpdateHostGroup(c *gin.Context) { var req dto.ChangeHostGroup @@ -228,3 +245,84 @@ func (b *BaseApi) UpdateHostGroup(c *gin.Context) { } helper.SuccessWithData(c, nil) } + +func (b *BaseApi) WsSsh(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() + + id, err := strconv.Atoi(c.Query("id")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) { + 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 + } + host, err := hostService.GetHostInfo(uint(id)) + if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) { + return + } + var connInfo ssh.ConnInfo + _ = copier.Copy(&connInfo, &host) + connInfo.PrivateKey = []byte(host.PrivateKey) + if len(host.PassPhrase) != 0 { + connInfo.PassPhrase = []byte(host.PassPhrase) + } + + client, err := connInfo.NewClient() + 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, true, connInfo.Client, wsConn) + if wshandleError(wsConn, err) { + return + } + defer sws.Close() + + quitChan := make(chan bool, 3) + sws.Start(quitChan) + go sws.Wait(quitChan) + + <-quitChan + + if wshandleError(wsConn, err) { + return + } +} + +var upGrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024 * 1024 * 10, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func wshandleError(ws *websocket.Conn, err error) bool { + if err != nil { + global.LOG.Errorf("handler ws faled:, err: %v", err) + dt := time.Now().Add(time.Second) + if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil { + wsData, err := json.Marshal(terminal.WsMsg{ + Type: terminal.WsMsgCmd, + Data: base64.StdEncoding.EncodeToString([]byte(err.Error())), + }) + if err != nil { + _ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}")) + } else { + _ = ws.WriteMessage(websocket.TextMessage, wsData) + } + } + return true + } + return false +} diff --git a/core/app/dto/command.go b/core/app/dto/command.go new file mode 100644 index 000000000..1369de72e --- /dev/null +++ b/core/app/dto/command.go @@ -0,0 +1,38 @@ +package dto + +type SearchCommandWithPage struct { + PageInfo + OrderBy string `json:"orderBy" validate:"required,oneof=name command created_at"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + GroupID uint `json:"groupID"` + Type string `josn:"type" validate:"required,oneof=redis command"` + Info string `json:"info"` +} + +type CommandOperate struct { + ID uint `json:"id"` + Type string `josn:"type"` + GroupID uint `json:"groupID"` + GroupBelong string `json:"groupBelong"` + Name string `json:"name" validate:"required"` + Command string `json:"command" validate:"required"` +} + +type CommandInfo struct { + ID uint `json:"id"` + GroupID uint `json:"groupID"` + Name string `json:"name"` + Command string `json:"command"` + GroupBelong string `json:"groupBelong"` +} + +type CommandTree struct { + ID uint `json:"id"` + Label string `json:"label"` + Children []CommandInfo `json:"children"` +} + +type CommandDelete struct { + Type string `json:"type" validate:"required,oneof=redis command"` + IDs []uint `json:"ids"` +} diff --git a/core/app/dto/common.go b/core/app/dto/common.go index b602b79df..3d8cc1212 100644 --- a/core/app/dto/common.go +++ b/core/app/dto/common.go @@ -31,6 +31,10 @@ type Options struct { Option string `json:"option"` } +type OperateByType struct { + Type string `json:"type"` +} + type OperateByID struct { ID uint `json:"id"` } diff --git a/agent/app/dto/group.go b/core/app/dto/group.go similarity index 100% rename from agent/app/dto/group.go rename to core/app/dto/group.go diff --git a/agent/app/dto/host.go b/core/app/dto/host.go similarity index 84% rename from agent/app/dto/host.go rename to core/app/dto/host.go index e82f221af..9853d8f34 100644 --- a/agent/app/dto/host.go +++ b/core/app/dto/host.go @@ -73,15 +73,3 @@ type TreeChild struct { ID uint `json:"id"` Label string `json:"label"` } - -type MonitorSetting struct { - MonitorStatus string `json:"monitorStatus"` - MonitorStoreDays string `json:"monitorStoreDays"` - MonitorInterval string `json:"monitorInterval"` - DefaultNetwork string `json:"defaultNetwork"` -} - -type MonitorSettingUpdate struct { - Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork"` - Value string `json:"value"` -} diff --git a/core/app/model/backup.go b/core/app/model/backup.go index 019a8fb61..375c08a92 100644 --- a/core/app/model/backup.go +++ b/core/app/model/backup.go @@ -1,5 +1,7 @@ package model +import "time" + type BackupAccount struct { BaseModel Name string `gorm:"unique;not null" json:"name"` @@ -11,6 +13,7 @@ type BackupAccount struct { Vars string `json:"vars"` RememberAuth bool `json:"rememberAuth"` - InUsed bool `json:"inUsed"` EntryID uint `json:"entryID"` + + DeletedAt time.Time `json:"deletedAt"` } diff --git a/core/app/model/command.go b/core/app/model/command.go new file mode 100644 index 000000000..0c76e13f4 --- /dev/null +++ b/core/app/model/command.go @@ -0,0 +1,9 @@ +package model + +type Command struct { + BaseModel + Type string `gorm:"not null" json:"type"` + Name string `gorm:"not null" json:"name"` + GroupID uint `gorm:"not null" json:"groupID"` + Command string `gorm:"not null" json:"command"` +} diff --git a/core/app/model/group.go b/core/app/model/group.go new file mode 100644 index 000000000..c8999b703 --- /dev/null +++ b/core/app/model/group.go @@ -0,0 +1,8 @@ +package model + +type Group struct { + BaseModel + IsDefault bool `json:"isDefault"` + Name string `json:"name"` + Type string `json:"type"` +} diff --git a/agent/app/model/host.go b/core/app/model/host.go similarity index 100% rename from agent/app/model/host.go rename to core/app/model/host.go diff --git a/agent/app/repo/command.go b/core/app/repo/command.go similarity index 59% rename from agent/app/repo/command.go rename to core/app/repo/command.go index e2e9c7124..a17a257c9 100644 --- a/agent/app/repo/command.go +++ b/core/app/repo/command.go @@ -1,8 +1,8 @@ package repo import ( - "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" "gorm.io/gorm" ) @@ -14,15 +14,10 @@ type ICommandRepo interface { WithByInfo(info string) DBOption Create(command *model.Command) error Update(id uint, vars map[string]interface{}) error + UpdateGroup(group, newGroup uint) error Delete(opts ...DBOption) error Get(opts ...DBOption) (model.Command, error) WithLikeName(name string) DBOption - - PageRedis(limit, offset int, opts ...DBOption) (int64, []model.RedisCommand, error) - GetRedis(opts ...DBOption) (model.RedisCommand, error) - GetRedisList(opts ...DBOption) ([]model.RedisCommand, error) - SaveRedis(command *model.RedisCommand) error - DeleteRedis(opts ...DBOption) error } func NewICommandRepo() ICommandRepo { @@ -39,16 +34,6 @@ func (u *CommandRepo) Get(opts ...DBOption) (model.Command, error) { return command, err } -func (u *CommandRepo) GetRedis(opts ...DBOption) (model.RedisCommand, error) { - var command model.RedisCommand - db := global.DB - for _, opt := range opts { - db = opt(db) - } - err := db.First(&command).Error - return command, err -} - func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Command, error) { var users []model.Command db := global.DB.Model(&model.Command{}) @@ -61,18 +46,6 @@ func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Com return count, users, err } -func (u *CommandRepo) PageRedis(page, size int, opts ...DBOption) (int64, []model.RedisCommand, error) { - var users []model.RedisCommand - db := global.DB.Model(&model.RedisCommand{}) - 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 *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) { var commands []model.Command db := global.DB.Model(&model.Command{}) @@ -83,16 +56,6 @@ func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) { return commands, err } -func (u *CommandRepo) GetRedisList(opts ...DBOption) ([]model.RedisCommand, error) { - var commands []model.RedisCommand - db := global.DB.Model(&model.RedisCommand{}) - for _, opt := range opts { - db = opt(db) - } - err := db.Find(&commands).Error - return commands, err -} - func (c *CommandRepo) WithByInfo(info string) DBOption { return func(g *gorm.DB) *gorm.DB { if len(info) == 0 { @@ -107,13 +70,12 @@ func (u *CommandRepo) Create(command *model.Command) error { return global.DB.Create(command).Error } -func (u *CommandRepo) SaveRedis(command *model.RedisCommand) error { - return global.DB.Save(command).Error -} - func (u *CommandRepo) Update(id uint, vars map[string]interface{}) error { return global.DB.Model(&model.Command{}).Where("id = ?", id).Updates(vars).Error } +func (h *CommandRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Command{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error +} func (u *CommandRepo) Delete(opts ...DBOption) error { db := global.DB @@ -123,14 +85,6 @@ func (u *CommandRepo) Delete(opts ...DBOption) error { return db.Delete(&model.Command{}).Error } -func (u *CommandRepo) DeleteRedis(opts ...DBOption) error { - db := global.DB - for _, opt := range opts { - db = opt(db) - } - return db.Delete(&model.RedisCommand{}).Error -} - func (a CommandRepo) WithLikeName(name string) DBOption { return func(g *gorm.DB) *gorm.DB { if len(name) == 0 { diff --git a/core/app/repo/common.go b/core/app/repo/common.go index 284ef6400..013db44ef 100644 --- a/core/app/repo/common.go +++ b/core/app/repo/common.go @@ -1,6 +1,9 @@ package repo import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/constant" "gorm.io/gorm" ) @@ -12,6 +15,8 @@ type ICommonRepo interface { WithByIDs(ids []uint) DBOption WithByType(ty string) DBOption WithOrderBy(orderStr string) DBOption + + WithOrderRuleBy(orderBy, order string) DBOption } type CommonRepo struct{} @@ -51,3 +56,18 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption { return g.Order(orderStr) } } + +func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption { + switch order { + case constant.OrderDesc: + order = "desc" + case constant.OrderAsc: + order = "asc" + default: + orderBy = "created_at" + order = "desc" + } + return func(g *gorm.DB) *gorm.DB { + return g.Order(fmt.Sprintf("%s %s", orderBy, order)) + } +} diff --git a/agent/app/repo/group.go b/core/app/repo/group.go similarity index 60% rename from agent/app/repo/group.go rename to core/app/repo/group.go index 1f5b27ae9..9ce1336ef 100644 --- a/agent/app/repo/group.go +++ b/core/app/repo/group.go @@ -1,8 +1,8 @@ package repo import ( - "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" "gorm.io/gorm" ) @@ -14,14 +14,42 @@ type IGroupRepo interface { Create(group *model.Group) error Update(id uint, vars map[string]interface{}) error Delete(opts ...DBOption) error + + WithByID(id uint) DBOption + WithByGroupID(id uint) DBOption + WithByGroupType(ty string) DBOption + WithByDefault(isDefalut bool) DBOption CancelDefault(groupType string) error - WithByHostDefault() DBOption } func NewIGroupRepo() IGroupRepo { return &GroupRepo{} } +func (c *GroupRepo) WithByID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func (c *GroupRepo) WithByGroupID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("group_id = ?", id) + } +} + +func (c *GroupRepo) WithByGroupType(ty string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`type` = ?", ty) + } +} + +func (c *GroupRepo) WithByDefault(isDefalut bool) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("is_default = ?", isDefalut) + } +} + func (u *GroupRepo) Get(opts ...DBOption) (model.Group, error) { var group model.Group db := global.DB @@ -50,12 +78,6 @@ func (u *GroupRepo) Update(id uint, vars map[string]interface{}) error { return global.DB.Model(&model.Group{}).Where("id = ?", id).Updates(vars).Error } -func (u *GroupRepo) WithByHostDefault() DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("is_default = ? AND type = ?", 1, "host") - } -} - func (u *GroupRepo) Delete(opts ...DBOption) error { db := global.DB for _, opt := range opts { @@ -65,5 +87,7 @@ func (u *GroupRepo) Delete(opts ...DBOption) error { } func (u *GroupRepo) CancelDefault(groupType string) error { - return global.DB.Model(&model.Group{}).Where("is_default = ? AND type = ?", 1, groupType).Updates(map[string]interface{}{"is_default": 0}).Error + return global.DB.Model(&model.Group{}). + Where("is_default = ? AND type = ?", 1, groupType). + Updates(map[string]interface{}{"is_default": 0}).Error } diff --git a/core/app/repo/host.go b/core/app/repo/host.go new file mode 100644 index 000000000..c0edb0faa --- /dev/null +++ b/core/app/repo/host.go @@ -0,0 +1,113 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type HostRepo struct{} + +type IHostRepo interface { + Get(opts ...DBOption) (model.Host, error) + GetList(opts ...DBOption) ([]model.Host, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error) + WithByInfo(info string) DBOption + WithByPort(port uint) DBOption + WithByUser(user string) DBOption + WithByAddr(addr string) DBOption + Create(host *model.Host) error + Update(id uint, vars map[string]interface{}) error + UpdateGroup(group, newGroup uint) error + Delete(opts ...DBOption) error +} + +func NewIHostRepo() IHostRepo { + return &HostRepo{} +} + +func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) { + var host model.Host + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&host).Error + return host, err +} + +func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { + var hosts []model.Host + db := global.DB.Model(&model.Host{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&hosts).Error + return hosts, err +} + +func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) { + var users []model.Host + db := global.DB.Model(&model.Host{}) + 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 (h *HostRepo) WithByInfo(info string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(info) == 0 { + return g + } + infoStr := "%" + info + "%" + return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr) + } +} + +func (h *HostRepo) WithByPort(port uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("port = ?", port) + } +} +func (h *HostRepo) WithByUser(user string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("user = ?", user) + } +} +func (h *HostRepo) WithByAddr(addr string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("addr = ?", addr) + } +} +func (h *HostRepo) WithByGroup(group string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(group) == 0 { + return g + } + return g.Where("group_belong = ?", group) + } +} + +func (h *HostRepo) Create(host *model.Host) error { + return global.DB.Create(host).Error +} + +func (h *HostRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error +} + +func (h *HostRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Host{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error +} + +func (h *HostRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Host{}).Error +} diff --git a/core/app/service/backup.go b/core/app/service/backup.go index 4308ed25f..987899f44 100644 --- a/core/app/service/backup.go +++ b/core/app/service/backup.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "net/http" "os" "path" "strings" @@ -19,6 +20,8 @@ import ( "github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client" "github.com/1Panel-dev/1Panel/core/utils/encrypt" fileUtils "github.com/1Panel-dev/1Panel/core/utils/files" + httpUtils "github.com/1Panel-dev/1Panel/core/utils/http" + "github.com/1Panel-dev/1Panel/core/utils/xpack" "github.com/jinzhu/copier" "github.com/pkg/errors" "github.com/robfig/cron/v3" @@ -271,12 +274,17 @@ func (u *BackupService) Delete(id uint) error { if backup.Type == constant.Local { return buserr.New(constant.ErrBackupLocalDelete) } - if backup.InUsed { - return buserr.New(constant.ErrBackupInUsed) - } if backup.Type == constant.OneDrive { global.Cron.Remove(cron.EntryID(backup.EntryID)) } + if _, err := httpUtils.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%v", id), http.MethodGet, nil); err != nil { + global.LOG.Errorf("check used of local cronjob failed, err: %v", err) + return buserr.New(constant.ErrBackupInUsed) + } + if err := xpack.CheckBackupUsed(id); err != nil { + global.LOG.Errorf("check used of node cronjob failed, err: %v", err) + return buserr.New(constant.ErrBackupInUsed) + } return backupRepo.Delete(commonRepo.WithByID(id)) } diff --git a/core/app/service/command.go b/core/app/service/command.go new file mode 100644 index 000000000..0fe1d4939 --- /dev/null +++ b/core/app/service/command.go @@ -0,0 +1,128 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type CommandService struct{} + +type ICommandService interface { + List(req dto.OperateByType) ([]dto.CommandInfo, error) + SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error) + SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) + Create(commandDto dto.CommandOperate) error + Update(id uint, upMap map[string]interface{}) error + Delete(ids []uint) error +} + +func NewICommandService() ICommandService { + return &CommandService{} +} + +func (u *CommandService) List(req dto.OperateByType) ([]dto.CommandInfo, error) { + commands, err := commandRepo.GetList(commonRepo.WithOrderBy("name"), commonRepo.WithByType(req.Type)) + if err != nil { + return nil, constant.ErrRecordNotFound + } + var dtoCommands []dto.CommandInfo + for _, command := range commands { + var item dto.CommandInfo + if err := copier.Copy(&item, &command); err != nil { + return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + dtoCommands = append(dtoCommands, item) + } + return dtoCommands, err +} + +func (u *CommandService) SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error) { + cmdList, err := commandRepo.GetList(commonRepo.WithOrderBy("name"), commonRepo.WithByType(req.Type)) + if err != nil { + return nil, err + } + groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type)) + if err != nil { + return nil, err + } + var lists []dto.CommandTree + for _, group := range groups { + var data dto.CommandTree + data.ID = group.ID + 10000 + data.Label = group.Name + for _, cmd := range cmdList { + if cmd.GroupID == group.ID { + data.Children = append(data.Children, dto.CommandInfo{ID: cmd.ID, Name: cmd.Name, Command: cmd.Command}) + } + } + if len(data.Children) != 0 { + lists = append(lists, data) + } + } + return lists, err +} + +func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, interface{}, error) { + options := []repo.DBOption{ + commonRepo.WithOrderRuleBy(req.OrderBy, req.Order), + commonRepo.WithByType(req.Type), + } + if len(req.Info) != 0 { + options = append(options, commandRepo.WithLikeName(req.Info)) + } + if req.GroupID != 0 { + options = append(options, groupRepo.WithByGroupID(req.GroupID)) + } + total, commands, err := commandRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + groups, _ := groupRepo.GetList(commonRepo.WithByType(req.Type)) + var dtoCommands []dto.CommandInfo + for _, command := range commands { + var item dto.CommandInfo + if err := copier.Copy(&item, &command); err != nil { + return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + for _, group := range groups { + if command.GroupID == group.ID { + item.GroupBelong = group.Name + item.GroupID = group.ID + } + } + dtoCommands = append(dtoCommands, item) + } + return total, dtoCommands, err +} + +func (u *CommandService) Create(commandDto dto.CommandOperate) error { + command, _ := commandRepo.Get(commonRepo.WithByName(commandDto.Name)) + if command.ID != 0 { + return constant.ErrRecordExist + } + if err := copier.Copy(&command, &commandDto); err != nil { + return errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + if err := commandRepo.Create(&command); err != nil { + return err + } + return nil +} + +func (u *CommandService) Delete(ids []uint) error { + if len(ids) == 1 { + command, _ := commandRepo.Get(commonRepo.WithByID(ids[0])) + if command.ID == 0 { + return constant.ErrRecordNotFound + } + return commandRepo.Delete(commonRepo.WithByID(ids[0])) + } + return commandRepo.Delete(commonRepo.WithByIDs(ids)) +} + +func (u *CommandService) Update(id uint, upMap map[string]interface{}) error { + return commandRepo.Update(id, upMap) +} diff --git a/core/app/service/entry.go b/core/app/service/entry.go index f6b25a2c6..229a7dfa5 100644 --- a/core/app/service/entry.go +++ b/core/app/service/entry.go @@ -3,8 +3,11 @@ package service import "github.com/1Panel-dev/1Panel/core/app/repo" var ( + hostRepo = repo.NewIHostRepo() + commandRepo = repo.NewICommandRepo() commonRepo = repo.NewICommonRepo() settingRepo = repo.NewISettingRepo() backupRepo = repo.NewIBackupRepo() logRepo = repo.NewILogRepo() + groupRepo = repo.NewIGroupRepo() ) diff --git a/agent/app/service/group.go b/core/app/service/group.go similarity index 52% rename from agent/app/service/group.go rename to core/app/service/group.go index 97ec2e2e6..33e3a8b80 100644 --- a/agent/app/service/group.go +++ b/core/app/service/group.go @@ -1,9 +1,17 @@ package service import ( - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/buserr" - "github.com/1Panel-dev/1Panel/agent/constant" + "bytes" + "fmt" + "net/http" + + "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/constant" + httpUtils "github.com/1Panel-dev/1Panel/core/utils/http" + "github.com/1Panel-dev/1Panel/core/utils/xpack" "github.com/jinzhu/copier" "github.com/pkg/errors" ) @@ -11,7 +19,7 @@ import ( type GroupService struct{} type IGroupService interface { - List(req dto.GroupSearch) ([]dto.GroupInfo, error) + List(req dto.OperateByType) ([]dto.GroupInfo, error) Create(req dto.GroupCreate) error Update(req dto.GroupUpdate) error Delete(id uint) error @@ -21,8 +29,17 @@ func NewIGroupService() IGroupService { return &GroupService{} } -func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) { - groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type), commonRepo.WithOrderBy("is_default desc"), commonRepo.WithOrderBy("created_at desc")) +func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) { + options := []repo.DBOption{ + commonRepo.WithByType(req.Type), + commonRepo.WithOrderBy("is_default desc"), + commonRepo.WithOrderBy("created_at desc"), + } + var ( + groups []model.Group + err error + ) + groups, err = groupRepo.GetList(options...) if err != nil { return nil, constant.ErrRecordNotFound } @@ -56,22 +73,33 @@ func (u *GroupService) Delete(id uint) error { if group.ID == 0 { return constant.ErrRecordNotFound } + if group.IsDefault { + return buserr.New(constant.ErrGroupIsDefault) + } + defaultGroup, err := groupRepo.Get(commonRepo.WithByType(group.Type), groupRepo.WithByDefault(true)) + if err != nil { + return err + } switch group.Type { - case "website": - websites, _ := websiteRepo.GetBy(websiteRepo.WithGroupID(id)) - if len(websites) > 0 { - return buserr.New(constant.ErrGroupIsUsed) - } - case "command": - commands, _ := commandRepo.GetList(commonRepo.WithByGroupID(id)) - if len(commands) > 0 { - return buserr.New(constant.ErrGroupIsUsed) - } case "host": - hosts, _ := hostRepo.GetList(commonRepo.WithByGroupID(id)) - if len(hosts) > 0 { - return buserr.New(constant.ErrGroupIsUsed) + err = hostRepo.UpdateGroup(id, defaultGroup.ID) + case "command": + err = commandRepo.UpdateGroup(id, defaultGroup.ID) + case "node": + err = xpack.UpdateGroup("node", id, defaultGroup.ID) + case "website": + bodyItem := []byte(fmt.Sprintf(`{"Group":%v, "NewGroup":%v}`, id, defaultGroup.ID)) + if _, err := httpUtils.NewLocalClient("/api/v2/websites/group/change", http.MethodPost, bytes.NewReader(bodyItem)); err != nil { + return err } + if err := xpack.UpdateGroup("node", id, defaultGroup.ID); err != nil { + return err + } + default: + return constant.ErrNotSupportType + } + if err != nil { + return err } return groupRepo.Delete(commonRepo.WithByID(id)) } diff --git a/agent/app/service/host.go b/core/app/service/host.go similarity index 90% rename from agent/app/service/host.go rename to core/app/service/host.go index 85a93fba3..30959239c 100644 --- a/agent/app/service/host.go +++ b/core/app/service/host.go @@ -4,11 +4,12 @@ import ( "encoding/base64" "fmt" - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/1Panel-dev/1Panel/agent/constant" - "github.com/1Panel-dev/1Panel/agent/utils/encrypt" - "github.com/1Panel-dev/1Panel/agent/utils/ssh" + "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/constant" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/ssh" "github.com/jinzhu/copier" "github.com/pkg/errors" ) @@ -149,8 +150,15 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) { return &host, err } -func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) { - total, hosts, err := hostRepo.Page(search.Page, search.PageSize, hostRepo.WithByInfo(search.Info), commonRepo.WithByGroupID(search.GroupID)) +func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interface{}, error) { + var options []repo.DBOption + if len(req.Info) != 0 { + options = append(options, commandRepo.WithLikeName(req.Info)) + } + if req.GroupID != 0 { + options = append(options, groupRepo.WithByGroupID(req.GroupID)) + } + total, hosts, err := hostRepo.Page(req.Page, req.PageSize, options...) if err != nil { return 0, nil, err } @@ -249,7 +257,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } if req.GroupID == 0 { - group, err := groupRepo.Get(groupRepo.WithByHostDefault()) + group, err := groupRepo.Get(commonRepo.WithByType("host"), groupRepo.WithByDefault(true)) if err != nil { return nil, errors.New("get default group failed") } @@ -299,7 +307,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { } func (u *HostService) Delete(ids []uint) error { - hosts, _ := hostRepo.GetList(commonRepo.WithIdsIn(ids)) + hosts, _ := hostRepo.GetList(commonRepo.WithByIDs(ids)) for _, host := range hosts { if host.ID == 0 { return constant.ErrRecordNotFound @@ -308,7 +316,7 @@ func (u *HostService) Delete(ids []uint) error { return errors.New("the local connection information cannot be deleted!") } } - return hostRepo.Delete(commonRepo.WithIdsIn(ids)) + return hostRepo.Delete(commonRepo.WithByIDs(ids)) } func (u *HostService) Update(id uint, upMap map[string]interface{}) error { diff --git a/core/constant/errs.go b/core/constant/errs.go index bbefe0bd7..0c776c204 100644 --- a/core/constant/errs.go +++ b/core/constant/errs.go @@ -37,6 +37,7 @@ var ( ErrPortInUsed = "ErrPortInUsed" ErrCmdTimeout = "ErrCmdTimeout" ErrGroupIsUsed = "ErrGroupIsUsed" + ErrGroupIsDefault = "ErrGroupIsDefault" ) // api diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 98f4b3eab..273ea35f0 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -14,6 +14,7 @@ ErrProxy: "请求错误,请检查该节点状态: {{ .detail }}" ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrCmdTimeout: "命令执行超时!" ErrEntrance: "安全入口信息错误,请检查后重试!" +ErrGroupIsDefault: '默认分组,无法删除' ErrGroupIsUsed: "分组正在使用中,无法删除" ErrLocalDelete: "无法删除本地节点!" ErrMasterAddr: "当前未设置主节点地址,无法添加节点!" diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 41f53d671..8b76f7e9f 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -13,6 +13,7 @@ func Init() { migrations.InitSetting, migrations.InitOneDrive, migrations.InitMasterAddr, + migrations.InitHost, }) 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 d8578f73f..d36af12a0 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -15,13 +15,16 @@ import ( ) var AddTable = &gormigrate.Migration{ - ID: "20240808-add-table", + ID: "20240819-add-table", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate( &model.OperationLog{}, &model.LoginLog{}, &model.Setting{}, &model.BackupAccount{}, + &model.Group{}, + &model.Host{}, + &model.Command{}, ) }, } @@ -144,6 +147,35 @@ var InitSetting = &gormigrate.Migration{ }, } +var InitHost = &gormigrate.Migration{ + ID: "20240816-init-host", + Migrate: func(tx *gorm.DB) error { + hostGroup := &model.Group{Name: "default", Type: "host", IsDefault: true} + if err := tx.Create(hostGroup).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "default", Type: "node", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "default", Type: "command", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "default", Type: "website", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "default", Type: "redis", IsDefault: true}).Error; err != nil { + return err + } + host := model.Host{ + Name: "localhost", Addr: "127.0.0.1", User: "root", Port: 22, AuthMode: "password", GroupID: hostGroup.ID, + } + if err := tx.Create(&host).Error; err != nil { + return err + } + return nil + }, +} + var InitOneDrive = &gormigrate.Migration{ ID: "20240808-init-one-drive", Migrate: func(tx *gorm.DB) error { diff --git a/core/router/command.go b/core/router/command.go new file mode 100644 index 000000000..16a2e0736 --- /dev/null +++ b/core/router/command.go @@ -0,0 +1,21 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/gin-gonic/gin" +) + +type CommandRouter struct{} + +func (s *CommandRouter) InitRouter(Router *gin.RouterGroup) { + commandRouter := Router.Group("commands") + baseApi := v2.ApiGroupApp.BaseApi + { + commandRouter.POST("/list", baseApi.ListCommand) + commandRouter.POST("", baseApi.CreateCommand) + commandRouter.POST("/del", baseApi.DeleteCommand) + commandRouter.POST("/search", baseApi.SearchCommand) + commandRouter.POST("/tree", baseApi.SearchCommandTree) + commandRouter.POST("/update", baseApi.UpdateCommand) + } +} diff --git a/core/router/common.go b/core/router/common.go index 78748f230..748d81896 100644 --- a/core/router/common.go +++ b/core/router/common.go @@ -6,5 +6,8 @@ func commonGroups() []CommonRouter { &BackupRouter{}, &LogRouter{}, &SettingRouter{}, + &CommandRouter{}, + &HostRouter{}, + &GroupRouter{}, } } diff --git a/agent/router/ro_group.go b/core/router/ro_group.go similarity index 68% rename from agent/router/ro_group.go rename to core/router/ro_group.go index 4cf92a080..827dd1446 100644 --- a/agent/router/ro_group.go +++ b/core/router/ro_group.go @@ -1,14 +1,14 @@ package router import ( - v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" "github.com/gin-gonic/gin" ) -type WebsiteGroupRouter struct { +type GroupRouter struct { } -func (a *WebsiteGroupRouter) InitRouter(Router *gin.RouterGroup) { +func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) { groupRouter := Router.Group("groups") baseApi := v2.ApiGroupApp.BaseApi diff --git a/core/router/ro_host.go b/core/router/ro_host.go new file mode 100644 index 000000000..0e03cdf8d --- /dev/null +++ b/core/router/ro_host.go @@ -0,0 +1,25 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/gin-gonic/gin" +) + +type HostRouter struct{} + +func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { + hostRouter := Router.Group("hosts") + baseApi := v2.ApiGroupApp.BaseApi + { + hostRouter.POST("", baseApi.CreateHost) + hostRouter.POST("/del", baseApi.DeleteHost) + hostRouter.POST("/update", baseApi.UpdateHost) + hostRouter.POST("/update/group", baseApi.UpdateHostGroup) + hostRouter.POST("/search", baseApi.SearchHost) + hostRouter.POST("/tree", baseApi.HostTree) + hostRouter.POST("/test/byinfo", baseApi.TestByInfo) + hostRouter.POST("/test/byid/:id", baseApi.TestByID) + + hostRouter.GET("/terminal", baseApi.WsSsh) + } +} diff --git a/core/utils/encrypt/encrypt.go b/core/utils/encrypt/encrypt.go index 5e596b73b..68cea03ef 100644 --- a/core/utils/encrypt/encrypt.go +++ b/core/utils/encrypt/encrypt.go @@ -5,7 +5,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "encoding/base32" "encoding/base64" "fmt" "io" @@ -31,7 +30,7 @@ func StringDecryptWithBase64(text string) (string, error) { if err != nil { return "", err } - return base32.StdEncoding.EncodeToString([]byte(decryptItem)), nil + return base64.StdEncoding.EncodeToString([]byte(decryptItem)), nil } func StringEncrypt(text string) (string, error) { diff --git a/core/utils/http/new.go b/core/utils/http/new.go new file mode 100644 index 000000000..1db6ecd18 --- /dev/null +++ b/core/utils/http/new.go @@ -0,0 +1,69 @@ +package http + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + + "github.com/1Panel-dev/1Panel/core/app/dto" +) + +func NewLocalClient(reqUrl, reqMethod string, body io.Reader) (interface{}, error) { + sockPath := "/tmp/agent.sock" + if _, err := os.Stat(sockPath); err != nil { + return nil, fmt.Errorf("no such agent.sock find in localhost, err: %v", err) + } + dialUnix := func() (conn net.Conn, err error) { + return net.Dial("unix", sockPath) + } + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialUnix() + }, + } + client := &http.Client{ + Transport: transport, + } + parsedURL, err := url.Parse("http://unix") + if err != nil { + return nil, fmt.Errorf("handle url Parse failed, err: %v \n", err) + } + rURL := &url.URL{ + Scheme: "http", + Path: reqUrl, + Host: parsedURL.Host, + } + + req, err := http.NewRequest(reqMethod, rURL.String(), body) + if err != nil { + return nil, fmt.Errorf("creating request failed, err: %v", err) + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("client do request failed, err: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("do request failed, err: %v", resp.Status) + } + bodyByte, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read resp body from request failed, err: %v", err) + } + var respJson dto.Response + if err := json.Unmarshal(bodyByte, &respJson); err != nil { + return nil, fmt.Errorf("json umarshal resp data failed, err: %v", err) + } + if respJson.Code != http.StatusOK { + return nil, fmt.Errorf("do request success but handle failed, err: %v", respJson.Message) + } + + return respJson.Data, nil +} diff --git a/core/utils/terminal/local_cmd.go b/core/utils/terminal/local_cmd.go new file mode 100644 index 000000000..56f023a29 --- /dev/null +++ b/core/utils/terminal/local_cmd.go @@ -0,0 +1,93 @@ +package terminal + +import ( + "os" + "os/exec" + "syscall" + "time" + "unsafe" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/creack/pty" + "github.com/pkg/errors" +) + +const ( + DefaultCloseSignal = syscall.SIGINT + DefaultCloseTimeout = 10 * time.Second +) + +type LocalCommand struct { + closeSignal syscall.Signal + closeTimeout time.Duration + + cmd *exec.Cmd + pty *os.File +} + +func NewCommand(commands []string) (*LocalCommand, error) { + cmd := exec.Command("docker", commands...) + + pty, err := pty.Start(cmd) + if err != nil { + return nil, errors.Wrapf(err, "failed to start command") + } + + lcmd := &LocalCommand{ + closeSignal: DefaultCloseSignal, + closeTimeout: DefaultCloseTimeout, + + cmd: cmd, + pty: pty, + } + + return lcmd, nil +} + +func (lcmd *LocalCommand) Read(p []byte) (n int, err error) { + return lcmd.pty.Read(p) +} + +func (lcmd *LocalCommand) Write(p []byte) (n int, err error) { + return lcmd.pty.Write(p) +} + +func (lcmd *LocalCommand) Close() error { + if lcmd.cmd != nil && lcmd.cmd.Process != nil { + _ = lcmd.cmd.Process.Kill() + } + _ = lcmd.pty.Close() + return nil +} + +func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error { + window := struct { + row uint16 + col uint16 + x uint16 + y uint16 + }{ + uint16(height), + uint16(width), + 0, + 0, + } + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + lcmd.pty.Fd(), + syscall.TIOCSWINSZ, + uintptr(unsafe.Pointer(&window)), + ) + if errno != 0 { + return errno + } else { + return nil + } +} + +func (lcmd *LocalCommand) Wait(quitChan chan bool) { + if err := lcmd.cmd.Wait(); err != nil { + global.LOG.Errorf("ssh session wait failed, err: %v", err) + setQuit(quitChan) + } +} diff --git a/core/utils/terminal/ws_local_session.go b/core/utils/terminal/ws_local_session.go new file mode 100644 index 000000000..2fd14e0ce --- /dev/null +++ b/core/utils/terminal/ws_local_session.go @@ -0,0 +1,122 @@ +package terminal + +import ( + "encoding/base64" + "encoding/json" + "sync" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +type LocalWsSession struct { + slave *LocalCommand + wsConn *websocket.Conn + + allowCtrlC bool + writeMutex sync.Mutex +} + +func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) { + if err := slave.ResizeTerminal(cols, rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + + return &LocalWsSession{ + slave: slave, + wsConn: wsConn, + + allowCtrlC: allowCtrlC, + }, nil +} + +func (sws *LocalWsSession) Start(quitChan chan bool) { + go sws.handleSlaveEvent(quitChan) + go sws.receiveWsMsg(quitChan) +} + +func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { + defer setQuit(exitCh) + defer global.LOG.Debug("thread of handle slave event has exited now") + + buffer := make([]byte, 1024) + for { + select { + case <-exitCh: + return + default: + n, _ := sws.slave.Read(buffer) + _ = sws.masterWrite(buffer[:n]) + } + } +} + +func (sws *LocalWsSession) masterWrite(data []byte) error { + sws.writeMutex.Lock() + defer sws.writeMutex.Unlock() + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(data), + }) + if err != nil { + return errors.Wrapf(err, "failed to encoding to json") + } + err = sws.wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + return errors.Wrapf(err, "failed to write to master") + } + return nil +} + +func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + defer global.LOG.Debug("thread of receive ws msg has exited now") + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + global.LOG.Errorf("reading webSocket message failed, err: %v", err) + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.slave.ResizeTerminal(msgObj.Cols, msgObj.Rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + if string(decodeBytes) != "\x03" || sws.allowCtrlC { + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + } + case WsMsgHeartbeat: + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.slave.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} diff --git a/core/utils/terminal/ws_session.go b/core/utils/terminal/ws_session.go new file mode 100644 index 000000000..c278ba947 --- /dev/null +++ b/core/utils/terminal/ws_session.go @@ -0,0 +1,218 @@ +package terminal + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gorilla/websocket" + "golang.org/x/crypto/ssh" +) + +type safeBuffer struct { + buffer bytes.Buffer + mu sync.Mutex +} + +func (w *safeBuffer) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Write(p) +} +func (w *safeBuffer) Bytes() []byte { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Bytes() +} +func (w *safeBuffer) Reset() { + w.mu.Lock() + defer w.mu.Unlock() + w.buffer.Reset() +} + +const ( + WsMsgCmd = "cmd" + WsMsgResize = "resize" + WsMsgHeartbeat = "heartbeat" +) + +type WsMsg struct { + Type string `json:"type"` + Data string `json:"data,omitempty"` // WsMsgCmd + Cols int `json:"cols,omitempty"` // WsMsgResize + Rows int `json:"rows,omitempty"` // WsMsgResize + Timestamp int `json:"timestamp,omitempty"` // WsMsgHeartbeat +} + +type LogicSshWsSession struct { + stdinPipe io.WriteCloser + comboOutput *safeBuffer + logBuff *safeBuffer + inputFilterBuff *safeBuffer + session *ssh.Session + wsConn *websocket.Conn + isAdmin bool + IsFlagged bool +} + +func NewLogicSshWsSession(cols, rows int, isAdmin bool, sshClient *ssh.Client, wsConn *websocket.Conn) (*LogicSshWsSession, error) { + sshSession, err := sshClient.NewSession() + if err != nil { + return nil, err + } + + stdinP, err := sshSession.StdinPipe() + if err != nil { + return nil, err + } + + comboWriter := new(safeBuffer) + logBuf := new(safeBuffer) + inputBuf := new(safeBuffer) + sshSession.Stdout = comboWriter + sshSession.Stderr = comboWriter + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.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 &LogicSshWsSession{ + stdinPipe: stdinP, + comboOutput: comboWriter, + logBuff: logBuf, + inputFilterBuff: inputBuf, + session: sshSession, + wsConn: wsConn, + isAdmin: isAdmin, + IsFlagged: false, + }, nil +} + +func (sws *LogicSshWsSession) Close() { + if sws.session != nil { + sws.session.Close() + } + if sws.logBuff != nil { + sws.logBuff = nil + } + if sws.comboOutput != nil { + sws.comboOutput = nil + } +} +func (sws *LogicSshWsSession) Start(quitChan chan bool) { + go sws.receiveWsMsg(quitChan) + go sws.sendComboOutput(quitChan) +} + +func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("[A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + case WsMsgHeartbeat: + // 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况 + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.stdinPipe.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} + +func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) { + wsConn := sws.wsConn + defer setQuit(exitCh) + + tick := time.NewTicker(time.Millisecond * time.Duration(60)) + defer tick.Stop() + for { + select { + case <-tick.C: + if sws.comboOutput == nil { + return + } + bs := sws.comboOutput.Bytes() + if len(bs) > 0 { + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(bs), + }) + if err != nil { + global.LOG.Errorf("encoding combo output to json failed, err: %v", err) + continue + } + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending combo output to webSocket failed, err: %v", err) + } + _, err = sws.logBuff.Write(bs) + if err != nil { + global.LOG.Errorf("combo output to log buffer failed, err: %v", err) + } + sws.comboOutput.buffer.Reset() + } + if string(bs) == string([]byte{13, 10, 108, 111, 103, 111, 117, 116, 13, 10}) { + sws.Close() + return + } + + case <-exitCh: + return + } + } +} + +func (sws *LogicSshWsSession) Wait(quitChan chan bool) { + if err := sws.session.Wait(); err != nil { + setQuit(quitChan) + } +} + +func setQuit(ch chan bool) { + ch <- true +} diff --git a/core/utils/xpack/xpack.go b/core/utils/xpack/xpack.go index 322a53b00..503a20e5b 100644 --- a/core/utils/xpack/xpack.go +++ b/core/utils/xpack/xpack.go @@ -9,3 +9,11 @@ import ( func Proxy(c *gin.Context, currentNode string) error { return nil } + +func UpdateGroup(name string, group, newGroup uint) error { + return nil +} + +func CheckBackupUsed(id uint) error { + return nil +} diff --git a/frontend/src/api/interface/command.ts b/frontend/src/api/interface/command.ts index fb81dc181..baa1449f9 100644 --- a/frontend/src/api/interface/command.ts +++ b/frontend/src/api/interface/command.ts @@ -1,19 +1,16 @@ export namespace Command { export interface CommandInfo { id: number; + type: string; name: string; groupID: number; command: string; } export interface CommandOperate { id: number; + type: string; name: string; groupID: number; command: string; } - export interface RedisCommand { - id: number; - name: string; - command: string; - } } diff --git a/frontend/src/api/interface/group.ts b/frontend/src/api/interface/group.ts index 4d925361f..728ba9427 100644 --- a/frontend/src/api/interface/group.ts +++ b/frontend/src/api/interface/group.ts @@ -4,6 +4,7 @@ export namespace Group { name: string; type: string; isDefault: boolean; + isDelete: boolean; } export interface GroupCreate { id: number; @@ -15,7 +16,4 @@ export namespace Group { name: string; isDefault: boolean; } - export interface GroupSearch { - type: string; - } } diff --git a/frontend/src/api/modules/backup.ts b/frontend/src/api/modules/backup.ts new file mode 100644 index 000000000..e609345f8 --- /dev/null +++ b/frontend/src/api/modules/backup.ts @@ -0,0 +1,82 @@ +import http from '@/api'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; +import { ResPage } from '../interface'; +import { Backup } from '../interface/backup'; +import { TimeoutEnum } from '@/enums/http-enum'; + +// backup-agent +export const handleBackup = (params: Backup.Backup) => { + return http.post(`/backups/backup`, params, TimeoutEnum.T_1H); +}; +export const handleRecover = (params: Backup.Recover) => { + return http.post(`/backups/recover`, params, TimeoutEnum.T_1D); +}; +export const handleRecoverByUpload = (params: Backup.Recover) => { + return http.post(`/backups/recover/byupload`, params, TimeoutEnum.T_1D); +}; +export const downloadBackupRecord = (params: Backup.RecordDownload) => { + return http.post(`/backups/record/download`, params, TimeoutEnum.T_10M); +}; +export const deleteBackupRecord = (params: { ids: number[] }) => { + return http.post(`/backups/record/del`, params); +}; +export const searchBackupRecords = (params: Backup.SearchBackupRecord) => { + return http.post>(`/backups/record/search`, params, TimeoutEnum.T_5M); +}; +export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => { + return http.post>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M); +}; +export const getFilesFromBackup = (type: string) => { + return http.post>(`/backups/search/files`, { type: type }); +}; + +// backup-core +export const refreshOneDrive = () => { + return http.post(`/core/backup/refresh/onedrive`, {}); +}; +export const getBackupList = () => { + return http.get>(`/core/backup/options`); +}; +export const getLocalBackupDir = () => { + return http.get(`/core/backup/local`); +}; +export const searchBackup = (params: Backup.SearchWithType) => { + return http.post>(`/core/backup/search`, params); +}; +export const getOneDriveInfo = () => { + return http.get(`/core/backup/onedrive`); +}; +export const addBackup = (params: Backup.BackupOperate) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + return http.post(`/core/backup`, request, TimeoutEnum.T_60S); +}; +export const editBackup = (params: Backup.BackupOperate) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + return http.post(`/core/backup/update`, request); +}; +export const deleteBackup = (params: { id: number }) => { + return http.post(`/core/backup/del`, params); +}; +export const listBucket = (params: Backup.ForBucket) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + return http.post(`/core/backup/buckets`, request); +}; diff --git a/frontend/src/api/modules/command.ts b/frontend/src/api/modules/command.ts new file mode 100644 index 000000000..32847127d --- /dev/null +++ b/frontend/src/api/modules/command.ts @@ -0,0 +1,22 @@ +import http from '@/api'; +import { ResPage, SearchWithPage } from '../interface'; +import { Command } from '../interface/command'; + +export const getCommandList = (type: string) => { + return http.post>(`/core/commands/list`, { type: type }); +}; +export const getCommandPage = (params: SearchWithPage) => { + return http.post>(`/core/commands/search`, params); +}; +export const getCommandTree = (type: string) => { + return http.post(`/core/commands/tree`, { type: type }); +}; +export const addCommand = (params: Command.CommandOperate) => { + return http.post(`/core/commands`, params); +}; +export const editCommand = (params: Command.CommandOperate) => { + return http.post(`/core/commands/update`, params); +}; +export const deleteCommand = (params: { ids: number[] }) => { + return http.post(`/core/commands/del`, params); +}; diff --git a/frontend/src/api/modules/group.ts b/frontend/src/api/modules/group.ts index 096f7c1f2..ec09de4e9 100644 --- a/frontend/src/api/modules/group.ts +++ b/frontend/src/api/modules/group.ts @@ -1,15 +1,15 @@ import { Group } from '../interface/group'; import http from '@/api'; -export const GetGroupList = (params: Group.GroupSearch) => { - return http.post>(`/groups/search`, params); +export const GetGroupList = (type: string) => { + return http.post>(`/core/groups/search`, { type: type }); }; export const CreateGroup = (params: Group.GroupCreate) => { - return http.post(`/groups`, params); + return http.post(`/core/groups`, params); }; export const UpdateGroup = (params: Group.GroupUpdate) => { - return http.post(`/groups/update`, params); + return http.post(`/core/groups/update`, params); }; export const DeleteGroup = (id: number) => { - return http.post(`/groups/del`, { id: id }); + return http.post(`/core/groups/del`, { id: id }); }; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 9c7eeb667..901d26ffa 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -1,90 +1,8 @@ import http from '@/api'; -import { ResPage, SearchWithPage } from '../interface'; -import { Command } from '../interface/command'; +import { ResPage } from '../interface'; import { Host } from '../interface/host'; -import { Base64 } from 'js-base64'; -import { deepCopy } from '@/utils/util'; import { TimeoutEnum } from '@/enums/http-enum'; -export const searchHosts = (params: Host.SearchWithPage) => { - return http.post>(`/hosts/search`, params); -}; -export const getHostTree = (params: Host.ReqSearch) => { - return http.post>(`/hosts/tree`, params); -}; -export const addHost = (params: Host.HostOperate) => { - let request = deepCopy(params) as Host.HostOperate; - if (request.password) { - request.password = Base64.encode(request.password); - } - if (request.privateKey) { - request.privateKey = Base64.encode(request.privateKey); - } - return http.post(`/hosts`, request); -}; -export const testByInfo = (params: Host.HostConnTest) => { - let request = deepCopy(params) as Host.HostOperate; - if (request.password) { - request.password = Base64.encode(request.password); - } - if (request.privateKey) { - request.privateKey = Base64.encode(request.privateKey); - } - return http.post(`/hosts/test/byinfo`, request); -}; -export const testByID = (id: number) => { - return http.post(`/hosts/test/byid/${id}`); -}; -export const editHost = (params: Host.HostOperate) => { - let request = deepCopy(params) as Host.HostOperate; - if (request.password) { - request.password = Base64.encode(request.password); - } - if (request.privateKey) { - request.privateKey = Base64.encode(request.privateKey); - } - return http.post(`/hosts/update`, request); -}; -export const editHostGroup = (params: Host.GroupChange) => { - return http.post(`/hosts/update/group`, params); -}; -export const deleteHost = (params: { ids: number[] }) => { - return http.post(`/hosts/del`, params); -}; - -// command -export const getCommandList = () => { - return http.get>(`/hosts/command`, {}); -}; -export const getCommandPage = (params: SearchWithPage) => { - return http.post>(`/hosts/command/search`, params); -}; -export const getCommandTree = () => { - return http.get(`/hosts/command/tree`); -}; -export const addCommand = (params: Command.CommandOperate) => { - return http.post(`/hosts/command`, params); -}; -export const editCommand = (params: Command.CommandOperate) => { - return http.post(`/hosts/command/update`, params); -}; -export const deleteCommand = (params: { ids: number[] }) => { - return http.post(`/hosts/command/del`, params); -}; - -export const getRedisCommandList = () => { - return http.get>(`/hosts/command/redis`, {}); -}; -export const getRedisCommandPage = (params: SearchWithPage) => { - return http.post>(`/hosts/command/redis/search`, params); -}; -export const saveRedisCommand = (params: Command.RedisCommand) => { - return http.post(`/hosts/command/redis`, params); -}; -export const deleteRedisCommand = (params: { ids: number[] }) => { - return http.post(`/hosts/command/redis/del`, params); -}; - // firewall export const loadFireBaseInfo = () => { return http.get(`/hosts/firewall/base`); diff --git a/frontend/src/api/modules/terminal.ts b/frontend/src/api/modules/terminal.ts new file mode 100644 index 000000000..3c46e12e1 --- /dev/null +++ b/frontend/src/api/modules/terminal.ts @@ -0,0 +1,51 @@ +import http from '@/api'; +import { ResPage } from '../interface'; +import { Host } from '../interface/host'; +import { Base64 } from 'js-base64'; +import { deepCopy } from '@/utils/util'; + +export const searchHosts = (params: Host.SearchWithPage) => { + return http.post>(`/core/hosts/search`, params); +}; +export const getHostTree = (params: Host.ReqSearch) => { + return http.post>(`/core/hosts/tree`, params); +}; +export const addHost = (params: Host.HostOperate) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + return http.post(`/core/hosts`, request); +}; +export const testByInfo = (params: Host.HostConnTest) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + return http.post(`/core/hosts/test/byinfo`, request); +}; +export const testByID = (id: number) => { + return http.post(`/core/hosts/test/byid/${id}`); +}; +export const editHost = (params: Host.HostOperate) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + return http.post(`/core/hosts/update`, request); +}; +export const editHostGroup = (params: Host.GroupChange) => { + return http.post(`/core/hosts/update/group`, params); +}; +export const deleteHost = (params: { ids: number[] }) => { + return http.post(`/core/hosts/del`, params); +}; diff --git a/frontend/src/assets/iconfont/iconfont.css b/frontend/src/assets/iconfont/iconfont.css index e5aedcbc5..fa1de0c3c 100644 --- a/frontend/src/assets/iconfont/iconfont.css +++ b/frontend/src/assets/iconfont/iconfont.css @@ -1,9 +1,9 @@ @font-face { font-family: "panel"; /* Project id 3575356 */ - src: url('iconfont.woff2?t=1717570629440') format('woff2'), - url('iconfont.woff?t=1717570629440') format('woff'), - url('iconfont.ttf?t=1717570629440') format('truetype'), - url('iconfont.svg?t=1717570629440#panel') format('svg'); + src: url('iconfont.woff2?t=1723798525783') format('woff2'), + url('iconfont.woff?t=1723798525783') format('woff'), + url('iconfont.ttf?t=1723798525783') format('truetype'), + url('iconfont.svg?t=1723798525783#panel') format('svg'); } .panel { @@ -14,6 +14,10 @@ -moz-osx-font-smoothing: grayscale; } +.p-zhongduan:before { + content: "\e731"; +} + .p-docker1:before { content: "\e76a"; } @@ -178,18 +182,10 @@ content: "\e856"; } -.p-webdav:before { - content: "\e622"; -} - .p-xiangqing:before { content: "\e677"; } -.p-onedrive:before { - content: "\e601"; -} - .p-caidan:before { content: "\e61d"; } @@ -198,14 +194,6 @@ content: "\e744"; } -.p-tengxunyun1:before { - content: "\e651"; -} - -.p-qiniuyun:before { - content: "\e62c"; -} - .p-file-png:before { content: "\e7ae"; } @@ -250,10 +238,6 @@ content: "\e60f"; } -.p-aws:before { - content: "\e600"; -} - .p-taolun:before { content: "\e602"; } @@ -262,22 +246,10 @@ content: "\e616"; } -.p-SFTP:before { - content: "\e647"; -} - .p-huaban88:before { content: "\e67c"; } -.p-oss:before { - content: "\e607"; -} - -.p-minio:before { - content: "\e63c"; -} - .p-logout:before { content: "\e8fe"; } diff --git a/frontend/src/assets/iconfont/iconfont.js b/frontend/src/assets/iconfont/iconfont.js index e7cd569dd..e980784df 100644 --- a/frontend/src/assets/iconfont/iconfont.js +++ b/frontend/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_3575356='',function(c){var l=(l=document.getElementsByTagName("script"))[l.length-1],a=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var h,t,p,z,v,i=function(l,a){a.parentNode.insertBefore(l,a)};if(a&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,a=document.createElement("div");a.innerHTML=c._iconfont_svg_string_3575356,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(l=document.body).firstChild?i(a,l.firstChild):l.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=h,z=c.document,v=!1,d(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,m())})}function m(){v||(v=!0,p())}function d(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(d,50)}m()}}(window); \ No newline at end of file +window._iconfont_svg_string_3575356='',function(c){var l=(l=document.getElementsByTagName("script"))[l.length-1],a=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var h,t,p,z,v,i=function(l,a){a.parentNode.insertBefore(l,a)};if(a&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,a=document.createElement("div");a.innerHTML=c._iconfont_svg_string_3575356,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(l=document.body).firstChild?i(a,l.firstChild):l.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=h,z=c.document,v=!1,d(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,m())})}function m(){v||(v=!0,p())}function d(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(d,50)}m()}}(window); \ No newline at end of file diff --git a/frontend/src/assets/iconfont/iconfont.json b/frontend/src/assets/iconfont/iconfont.json index 22ab3cc64..356677049 100644 --- a/frontend/src/assets/iconfont/iconfont.json +++ b/frontend/src/assets/iconfont/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "p-", "description": "", "glyphs": [ + { + "icon_id": "30053592", + "name": "终端", + "font_class": "zhongduan", + "unicode": "e731", + "unicode_decimal": 59185 + }, { "icon_id": "1064806", "name": "docker", @@ -292,13 +299,6 @@ "unicode": "e856", "unicode_decimal": 59478 }, - { - "icon_id": "23044673", - "name": "webdav", - "font_class": "webdav", - "unicode": "e622", - "unicode_decimal": 58914 - }, { "icon_id": "10293150", "name": "详情", @@ -306,13 +306,6 @@ "unicode": "e677", "unicode_decimal": 58999 }, - { - "icon_id": "13015332", - "name": "onedrive", - "font_class": "onedrive", - "unicode": "e601", - "unicode_decimal": 58881 - }, { "icon_id": "7708032", "name": "菜单", @@ -327,20 +320,6 @@ "unicode": "e744", "unicode_decimal": 59204 }, - { - "icon_id": "12959160", - "name": "腾讯云", - "font_class": "tengxunyun1", - "unicode": "e651", - "unicode_decimal": 58961 - }, - { - "icon_id": "24877229", - "name": "七牛云", - "font_class": "qiniuyun", - "unicode": "e62c", - "unicode_decimal": 58924 - }, { "icon_id": "19671162", "name": "png-1", @@ -418,13 +397,6 @@ "unicode": "e60f", "unicode_decimal": 58895 }, - { - "icon_id": "32101973", - "name": "Amazon_Web_Services_Logo", - "font_class": "aws", - "unicode": "e600", - "unicode_decimal": 58880 - }, { "icon_id": "1760690", "name": "讨论", @@ -439,13 +411,6 @@ "unicode": "e616", "unicode_decimal": 58902 }, - { - "icon_id": "13532955", - "name": "SFTP", - "font_class": "SFTP", - "unicode": "e647", - "unicode_decimal": 58951 - }, { "icon_id": "15337722", "name": "Logo GitHub", @@ -453,20 +418,6 @@ "unicode": "e67c", "unicode_decimal": 59004 }, - { - "icon_id": "16268521", - "name": "oss", - "font_class": "oss", - "unicode": "e607", - "unicode_decimal": 58887 - }, - { - "icon_id": "20290513", - "name": "minio", - "font_class": "minio", - "unicode": "e63c", - "unicode_decimal": 58940 - }, { "icon_id": "924436", "name": "logout", diff --git a/frontend/src/assets/iconfont/iconfont.svg b/frontend/src/assets/iconfont/iconfont.svg index 6d72b92c2..e89877662 100644 --- a/frontend/src/assets/iconfont/iconfont.svg +++ b/frontend/src/assets/iconfont/iconfont.svg @@ -14,6 +14,8 @@ /> + + @@ -96,20 +98,12 @@ - - - - - - - - @@ -132,20 +126,12 @@ - - - - - - - - diff --git a/frontend/src/assets/iconfont/iconfont.ttf b/frontend/src/assets/iconfont/iconfont.ttf index 9a67b14c0..136796171 100644 Binary files a/frontend/src/assets/iconfont/iconfont.ttf and b/frontend/src/assets/iconfont/iconfont.ttf differ diff --git a/frontend/src/assets/iconfont/iconfont.woff b/frontend/src/assets/iconfont/iconfont.woff index df3003dd6..c703ffddf 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff and b/frontend/src/assets/iconfont/iconfont.woff differ diff --git a/frontend/src/assets/iconfont/iconfont.woff2 b/frontend/src/assets/iconfont/iconfont.woff2 index f78318842..a8afcfaf9 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff2 and b/frontend/src/assets/iconfont/iconfont.woff2 differ diff --git a/frontend/src/components/group/change.vue b/frontend/src/components/group/change.vue index c87b3c273..4ddc05735 100644 --- a/frontend/src/components/group/change.vue +++ b/frontend/src/components/group/change.vue @@ -72,7 +72,7 @@ const rules = reactive({ }); const loadGroups = async (groupName: string) => { - const res = await GetGroupList({ type: dialogData.value.groupType }); + const res = await GetGroupList(dialogData.value.groupType); groupList.value = res.data; for (const group of groupList.value) { if (group.name === groupName) { diff --git a/frontend/src/components/group/index.vue b/frontend/src/components/group/index.vue index 6b883ab0e..7a1138b77 100644 --- a/frontend/src/components/group/index.vue +++ b/frontend/src/components/group/index.vue @@ -21,7 +21,13 @@ {{ $t('commons.table.default') }} {{ row.name }} - ({{ $t('commons.table.default') }}) + + ({{ $t('commons.table.default') }}) + + + + {{ $t('app.takeDown') }} + @@ -31,6 +37,24 @@ + + + + +