1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 16:29:17 +08:00

feat: 提取分组及快速命令功能 (#6169)

This commit is contained in:
ssongliu 2024-08-19 18:04:43 +08:00 committed by GitHub
parent 7fb42d7b47
commit 37df602ae8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 1657 additions and 963 deletions

View File

@ -10,6 +10,21 @@ import (
"github.com/gin-gonic/gin" "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 // @Tags Backup Account
// @Summary Page backup records // @Summary Page backup records
// @Description 获取备份记录列表分页 // @Description 获取备份记录列表分页

View File

@ -30,8 +30,6 @@ var (
cronjobService = service.NewICronjobService() cronjobService = service.NewICronjobService()
hostService = service.NewIHostService()
groupService = service.NewIGroupService()
fileService = service.NewIFileService() fileService = service.NewIFileService()
sshService = service.NewISSHService() sshService = service.NewISSHService()
firewallService = service.NewIFirewallService() firewallService = service.NewIFirewallService()
@ -45,8 +43,6 @@ var (
settingService = service.NewISettingService() settingService = service.NewISettingService()
backupService = service.NewIBackupService() backupService = service.NewIBackupService()
commandService = service.NewICommandService()
websiteService = service.NewIWebsiteService() websiteService = service.NewIWebsiteService()
websiteDnsAccountService = service.NewIWebsiteDnsAccountService() websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
websiteSSLService = service.NewIWebsiteSSLService() websiteSSLService = service.NewIWebsiteSSLService()

View File

@ -12,67 +12,12 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd" "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/1Panel-dev/1Panel/agent/utils/terminal"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors" "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) { func (b *BaseApi) RedisWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {

View File

@ -957,3 +957,15 @@ func (b *BaseApi) UpdateLoadBalanceFile(c *gin.Context) {
} }
helper.SuccessWithOutData(c) 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)
}

View File

@ -52,3 +52,8 @@ type OperationWithNameAndType struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`
} }
type UpdateGroup struct {
Group uint `json:"group"`
NewGroup uint `json:"newGroup"`
}

View File

@ -14,3 +14,15 @@ type MonitorData struct {
Date []time.Time `json:"date"` Date []time.Time `json:"date"`
Value []interface{} `json:"value"` 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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -21,7 +21,7 @@ type ICronjobRepo interface {
Create(cronjob *model.Cronjob) error Create(cronjob *model.Cronjob) error
WithByJobID(id int) DBOption WithByJobID(id int) DBOption
WithByDbName(name string) DBOption WithByDbName(name string) DBOption
WithByDefaultDownload(account string) DBOption WithByDownloadAccountID(id uint) DBOption
WithByRecordDropID(id int) DBOption WithByRecordDropID(id int) DBOption
WithByRecordFile(file string) DBOption WithByRecordFile(file string) DBOption
Save(id uint, cronjob model.Cronjob) error 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 func(g *gorm.DB) *gorm.DB {
return g.Where("default_download = ?", account) return g.Where("download_account_id = ?", id)
} }
} }

View File

@ -15,6 +15,7 @@ type IFtpRepo interface {
Create(ftp *model.Ftp) error Create(ftp *model.Ftp) error
Update(id uint, vars map[string]interface{}) error Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error Delete(opts ...DBOption) error
WithLikeUser(user string) DBOption
WithByUser(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 { 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 { return func(g *gorm.DB) *gorm.DB {
if len(user) == 0 { if len(user) == 0 {
return g return g

View File

@ -3,23 +3,11 @@ package repo
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
) )
type HostRepo struct{} type HostRepo struct{}
type IHostRepo interface { 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) GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
ListFirewallRecord() ([]model.Firewall, error) ListFirewallRecord() ([]model.Firewall, error)
SaveFirewallRecord(firewall *model.Firewall) error SaveFirewallRecord(firewall *model.Firewall) error
@ -31,88 +19,6 @@ func NewIHostRepo() IHostRepo {
return &HostRepo{} 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) { func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
var firewall model.Firewall var firewall model.Firewall
db := global.DB db := global.DB

View File

@ -31,6 +31,8 @@ type IWebsiteRepo interface {
DeleteBy(ctx context.Context, opts ...DBOption) error DeleteBy(ctx context.Context, opts ...DBOption) error
Create(ctx context.Context, app *model.Website) error Create(ctx context.Context, app *model.Website) error
DeleteAll(ctx context.Context) error DeleteAll(ctx context.Context) error
UpdateGroup(group, newGroup uint) error
} }
func NewIWebsiteRepo() IWebsiteRepo { 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 { func (w *WebsiteRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1 ").Delete(&model.Website{}).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
}

View File

@ -3,17 +3,20 @@ package service
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"path" "path"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "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/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage" "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
@ -26,6 +29,8 @@ import (
type BackupService struct{} type BackupService struct{}
type IBackupService interface { type IBackupService interface {
CheckUsed(id uint) error
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
DownloadRecord(info dto.DownloadRecord) (string, error) DownloadRecord(info dto.DownloadRecord) (string, error)
@ -97,6 +102,22 @@ func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchBy
return total, datas, err 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 { type loadSizeHelper struct {
isOk bool isOk bool
backupPath string backupPath string
@ -309,7 +330,15 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
accounts[i].Credential, _ = encrypt.StringDecryptWithKey(accounts[i].Credential, setting.Value) accounts[i].Credential, _ = encrypt.StringDecryptWithKey(accounts[i].Credential, setting.Value)
} }
} else { } 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 { if err != nil {
return nil, err return nil, err
} }
@ -327,6 +356,18 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
} }
clientMap := make(map[string]backupClientHelper) clientMap := make(map[string]backupClientHelper)
for _, item := range accounts { 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) backClient, err := newClient(&item)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -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))
}

View File

@ -22,8 +22,6 @@ var (
cronjobRepo = repo.NewICronjobRepo() cronjobRepo = repo.NewICronjobRepo()
hostRepo = repo.NewIHostRepo() hostRepo = repo.NewIHostRepo()
groupRepo = repo.NewIGroupRepo()
commandRepo = repo.NewICommandRepo()
ftpRepo = repo.NewIFtpRepo() ftpRepo = repo.NewIFtpRepo()
clamRepo = repo.NewIClamRepo() clamRepo = repo.NewIClamRepo()
monitorRepo = repo.NewIMonitorRepo() monitorRepo = repo.NewIMonitorRepo()

View File

@ -74,7 +74,7 @@ func (u *FtpService) Operate(operation string) error {
} }
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, 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 { if err != nil {
return 0, nil, err return 0, nil, err
} }
@ -142,7 +142,7 @@ func (f *FtpService) Create(req dto.FtpCreate) (uint, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User)) userInDB, _ := ftpRepo.Get(ftpRepo.WithByUser(req.User))
if userInDB.ID != 0 { if userInDB.ID != 0 {
return 0, constant.ErrRecordExist return 0, constant.ErrRecordExist
} }

View File

@ -181,10 +181,6 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil { if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err) 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) _, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)

View File

@ -10,7 +10,6 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/app/task"
"os" "os"
"path" "path"
"reflect" "reflect"
@ -20,6 +19,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
@ -114,6 +115,8 @@ type IWebsiteService interface {
DeleteLoadBalance(req request.WebsiteLBDelete) error DeleteLoadBalance(req request.WebsiteLBDelete) error
UpdateLoadBalance(req request.WebsiteLBUpdate) error UpdateLoadBalance(req request.WebsiteLBUpdate) error
UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error
ChangeGroup(group, newGroup uint) error
} }
func NewIWebsiteService() IWebsiteService { func NewIWebsiteService() IWebsiteService {
@ -3193,3 +3196,7 @@ func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) e
}() }()
return opNginx(nginxInstall.ContainerName, constant.NginxReload) return opNginx(nginxInstall.ContainerName, constant.NginxReload)
} }
func (w WebsiteService) ChangeGroup(group, newGroup uint) error {
return websiteRepo.UpdateGroup(group, newGroup)
}

View File

@ -11,10 +11,8 @@ func Init() {
m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{ m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{
migrations.AddTable, migrations.AddTable,
migrations.AddMonitorTable, migrations.AddMonitorTable,
migrations.InitHost,
migrations.InitSetting, migrations.InitSetting,
migrations.InitImageRepo, migrations.InitImageRepo,
migrations.InitDefaultGroup,
migrations.InitDefaultCA, migrations.InitDefaultCA,
migrations.InitPHPExtensions, migrations.InitPHPExtensions,
migrations.AddTask, migrations.AddTask,

View File

@ -28,7 +28,6 @@ var AddTable = &gormigrate.Migration{
&model.App{}, &model.App{},
&model.BackupRecord{}, &model.BackupRecord{},
&model.Clam{}, &model.Clam{},
&model.Command{},
&model.ComposeTemplate{}, &model.ComposeTemplate{},
&model.Compose{}, &model.Compose{},
&model.Cronjob{}, &model.Cronjob{},
@ -39,15 +38,12 @@ var AddTable = &gormigrate.Migration{
&model.Forward{}, &model.Forward{},
&model.Firewall{}, &model.Firewall{},
&model.Ftp{}, &model.Ftp{},
&model.Group{},
&model.Host{},
&model.ImageRepo{}, &model.ImageRepo{},
&model.JobRecords{}, &model.JobRecords{},
&model.MonitorBase{}, &model.MonitorBase{},
&model.MonitorIO{}, &model.MonitorIO{},
&model.MonitorNetwork{}, &model.MonitorNetwork{},
&model.PHPExtensions{}, &model.PHPExtensions{},
&model.RedisCommand{},
&model.Runtime{}, &model.Runtime{},
&model.Setting{}, &model.Setting{},
&model.Snapshot{}, &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{ var InitSetting = &gormigrate.Migration{
ID: "20240722-init-setting", ID: "20240722-init-setting",
Migrate: func(tx *gorm.DB) error { 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{ var InitDefaultCA = &gormigrate.Migration{
ID: "20240722-init-default-ca", ID: "20240722-init-default-ca",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {

24
agent/router/backup.go Normal file
View File

@ -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)
}
}

View File

@ -8,12 +8,11 @@ func commonGroups() []CommonRouter {
&LogRouter{}, &LogRouter{},
&FileRouter{}, &FileRouter{},
&ToolboxRouter{}, &ToolboxRouter{},
&TerminalRouter{},
&CronjobRouter{}, &CronjobRouter{},
&BackupRouter{},
&SettingRouter{}, &SettingRouter{},
&AppRouter{}, &AppRouter{},
&WebsiteRouter{}, &WebsiteRouter{},
&WebsiteGroupRouter{},
&WebsiteDnsAccountRouter{}, &WebsiteDnsAccountRouter{},
&WebsiteAcmeAccountRouter{}, &WebsiteAcmeAccountRouter{},
&WebsiteSSLRouter{}, &WebsiteSSLRouter{},

View File

@ -11,15 +11,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter := Router.Group("hosts") hostRouter := Router.Group("hosts")
baseApi := v2.ApiGroupApp.BaseApi 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.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule) hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall) 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/conffile/update", baseApi.UpdateSSHByfile)
hostRouter.POST("/ssh/operate", baseApi.OperateSSH) 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", baseApi.GetToolStatus)
hostRouter.POST("/tool/init", baseApi.InitToolConfig) hostRouter.POST("/tool/init", baseApi.InitToolConfig)
hostRouter.POST("/tool/operate", baseApi.OperateTool) hostRouter.POST("/tool/operate", baseApi.OperateTool)

View File

@ -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)
}
}

View File

@ -24,6 +24,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.GET("/:id", baseApi.GetWebsite) websiteRouter.GET("/:id", baseApi.GetWebsite)
websiteRouter.POST("/del", baseApi.DeleteWebsite) websiteRouter.POST("/del", baseApi.DeleteWebsite)
websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer) websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer)
websiteRouter.POST("/group/change", baseApi.ChangeWebsiteGroup)
websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains) websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain) websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain)

View File

@ -1,9 +1,9 @@
package v2 package v2
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/core/constant"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -14,7 +14,7 @@ import (
// @Param request body dto.CommandOperate true "request" // @Param request body dto.CommandOperate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name","command"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建快捷命令 [name][command]","formatEN":"create quick command [name][command]"}
func (b *BaseApi) CreateCommand(c *gin.Context) { func (b *BaseApi) CreateCommand(c *gin.Context) {
var req dto.CommandOperate var req dto.CommandOperate
@ -29,28 +29,6 @@ func (b *BaseApi) CreateCommand(c *gin.Context) {
helper.SuccessWithData(c, nil) 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 // @Tags Command
// @Summary Page commands // @Summary Page commands
// @Description 获取快速命令列表分页 // @Description 获取快速命令列表分页
@ -58,7 +36,7 @@ func (b *BaseApi) SaveRedisCommand(c *gin.Context) {
// @Param request body dto.SearchWithPage true "request" // @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult // @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/command/search [post] // @Router /core/commands/search [post]
func (b *BaseApi) SearchCommand(c *gin.Context) { func (b *BaseApi) SearchCommand(c *gin.Context) {
var req dto.SearchCommandWithPage var req dto.SearchCommandWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil { 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 // @Tags Command
// @Summary Tree commands // @Summary Tree commands
// @Description 获取快速命令树 // @Description 获取快速命令树
// @Accept json // @Accept json
// @Param request body dto.OperateByType true "request"
// @Success 200 {Array} dto.CommandTree // @Success 200 {Array} dto.CommandTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/command/tree [get] // @Router /core/commands/tree [get]
func (b *BaseApi) SearchCommandTree(c *gin.Context) { func (b *BaseApi) SearchCommandTree(c *gin.Context) {
list, err := commandService.SearchForTree() var req dto.OperateByType
if err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
helper.SuccessWithData(c, list) list, err := commandService.SearchForTree(req)
}
// @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()
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -139,11 +81,18 @@ func (b *BaseApi) ListRedisCommand(c *gin.Context) {
// @Tags Command // @Tags Command
// @Summary List commands // @Summary List commands
// @Description 获取快速命令列表 // @Description 获取快速命令列表
// @Accept json
// @Param request body dto.OperateByType true "request"
// @Success 200 {object} dto.CommandInfo // @Success 200 {object} dto.CommandInfo
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/command [get] // @Router /core/commands/command [get]
func (b *BaseApi) ListCommand(c *gin.Context) { 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 { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -156,40 +105,18 @@ func (b *BaseApi) ListCommand(c *gin.Context) {
// @Summary Delete command // @Summary Delete command
// @Description 删除快速命令 // @Description 删除快速命令
// @Accept json // @Accept json
// @Param request body dto.BatchDeleteReq true "request" // @Param request body dto.OperateByIDs true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @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) { func (b *BaseApi) DeleteCommand(c *gin.Context) {
var req dto.BatchDeleteReq var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }
if err := commandService.Delete(req.Ids); err != nil { 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 {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
@ -203,7 +130,7 @@ func (b *BaseApi) DeleteRedisCommand(c *gin.Context) {
// @Param request body dto.CommandOperate true "request" // @Param request body dto.CommandOperate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新快捷命令 [name]","formatEN":"update quick command [name]"}
func (b *BaseApi) UpdateCommand(c *gin.Context) { func (b *BaseApi) UpdateCommand(c *gin.Context) {
var req dto.CommandOperate var req dto.CommandOperate

View File

@ -9,9 +9,12 @@ type ApiGroup struct {
var ApiGroupApp = new(ApiGroup) var ApiGroupApp = new(ApiGroup)
var ( var (
hostService = service.NewIHostService()
authService = service.NewIAuthService() authService = service.NewIAuthService()
backupService = service.NewIBackupService() backupService = service.NewIBackupService()
settingService = service.NewISettingService() settingService = service.NewISettingService()
logService = service.NewILogService() logService = service.NewILogService()
upgradeService = service.NewIUpgradeService() upgradeService = service.NewIUpgradeService()
groupService = service.NewIGroupService()
commandService = service.NewICommandService()
) )

View File

@ -1,9 +1,9 @@
package v2 package v2
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/core/constant"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -14,7 +14,7 @@ import (
// @Param request body dto.GroupCreate true "request" // @Param request body dto.GroupCreate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"}
func (b *BaseApi) CreateGroup(c *gin.Context) { func (b *BaseApi) CreateGroup(c *gin.Context) {
var req dto.GroupCreate var req dto.GroupCreate
@ -36,7 +36,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
// @Param request body dto.OperateByID true "request" // @Param request body dto.OperateByID true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @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) { func (b *BaseApi) DeleteGroup(c *gin.Context) {
var req dto.OperateByID var req dto.OperateByID
@ -58,7 +58,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) {
// @Param request body dto.GroupUpdate true "request" // @Param request body dto.GroupUpdate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"}
func (b *BaseApi) UpdateGroup(c *gin.Context) { func (b *BaseApi) UpdateGroup(c *gin.Context) {
var req dto.GroupUpdate var req dto.GroupUpdate
@ -78,11 +78,11 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
// @Description 查询系统组 // @Description 查询系统组
// @Accept json // @Accept json
// @Param request body dto.GroupSearch true "request" // @Param request body dto.GroupSearch true "request"
// @Success 200 {array} dto.GroupInfo // @Success 200 {array} dto.OperateByType
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /groups/search [post] // @Router /core/groups/search [post]
func (b *BaseApi) ListGroup(c *gin.Context) { func (b *BaseApi) ListGroup(c *gin.Context) {
var req dto.GroupSearch var req dto.OperateByType
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }

View File

@ -1,11 +1,23 @@
package v2 package v2
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "encoding/base64"
"github.com/1Panel-dev/1Panel/agent/app/dto" "encoding/json"
"github.com/1Panel-dev/1Panel/agent/constant" "net/http"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt" "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/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
) )
// @Tags Host // @Tags Host
@ -15,7 +27,7 @@ import (
// @Param request body dto.HostOperate true "request" // @Param request body dto.HostOperate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"}
func (b *BaseApi) CreateHost(c *gin.Context) { func (b *BaseApi) CreateHost(c *gin.Context) {
var req dto.HostOperate var req dto.HostOperate
@ -38,7 +50,7 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
// @Param request body dto.HostConnTest true "request" // @Param request body dto.HostConnTest true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/test/byinfo [post] // @Router /core/hosts/test/byinfo [post]
func (b *BaseApi) TestByInfo(c *gin.Context) { func (b *BaseApi) TestByInfo(c *gin.Context) {
var req dto.HostConnTest var req dto.HostConnTest
if err := helper.CheckBindAndValidate(&req, c); err != nil { 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" // @Param id path integer true "request"
// @Success 200 {boolean} connStatus // @Success 200 {boolean} connStatus
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/test/byid/:id [post] // @Router /core/hosts/test/byid/:id [post]
func (b *BaseApi) TestByID(c *gin.Context) { 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 { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
connStatus := hostService.TestLocalConn(id) connStatus := hostService.TestLocalConn(uint(intNum))
helper.SuccessWithData(c, connStatus) helper.SuccessWithData(c, connStatus)
} }
@ -75,10 +92,10 @@ func (b *BaseApi) TestByID(c *gin.Context) {
// @Param request body dto.SearchForTree true "request" // @Param request body dto.SearchForTree true "request"
// @Success 200 {array} dto.HostTree // @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/tree [post] // @Router /core/hosts/tree [post]
func (b *BaseApi) HostTree(c *gin.Context) { func (b *BaseApi) HostTree(c *gin.Context) {
var req dto.SearchForTree var req dto.SearchForTree
if err := helper.CheckBind(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }
@ -98,7 +115,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Param request body dto.SearchHostWithPage true "request" // @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {array} dto.HostTree // @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/search [post] // @Router /core/hosts/search [post]
func (b *BaseApi) SearchHost(c *gin.Context) { func (b *BaseApi) SearchHost(c *gin.Context) {
var req dto.SearchHostWithPage var req dto.SearchHostWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil { 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" // @Param request body dto.BatchDeleteReq true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @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) { func (b *BaseApi) DeleteHost(c *gin.Context) {
var req dto.BatchDeleteReq var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return 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) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
@ -146,7 +163,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
// @Param request body dto.HostOperate true "request" // @Param request body dto.HostOperate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机信息 [name][addr]","formatEN":"update host [name][addr]"}
func (b *BaseApi) UpdateHost(c *gin.Context) { func (b *BaseApi) UpdateHost(c *gin.Context) {
var req dto.HostOperate var req dto.HostOperate
@ -212,7 +229,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
// @Param request body dto.ChangeHostGroup true "request" // @Param request body dto.ChangeHostGroup true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @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]"} // @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) { func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
var req dto.ChangeHostGroup var req dto.ChangeHostGroup
@ -228,3 +245,84 @@ func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
} }
helper.SuccessWithData(c, nil) 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
}

38
core/app/dto/command.go Normal file
View File

@ -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"`
}

View File

@ -31,6 +31,10 @@ type Options struct {
Option string `json:"option"` Option string `json:"option"`
} }
type OperateByType struct {
Type string `json:"type"`
}
type OperateByID struct { type OperateByID struct {
ID uint `json:"id"` ID uint `json:"id"`
} }

View File

@ -73,15 +73,3 @@ type TreeChild struct {
ID uint `json:"id"` ID uint `json:"id"`
Label string `json:"label"` 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"`
}

View File

@ -1,5 +1,7 @@
package model package model
import "time"
type BackupAccount struct { type BackupAccount struct {
BaseModel BaseModel
Name string `gorm:"unique;not null" json:"name"` Name string `gorm:"unique;not null" json:"name"`
@ -11,6 +13,7 @@ type BackupAccount struct {
Vars string `json:"vars"` Vars string `json:"vars"`
RememberAuth bool `json:"rememberAuth"` RememberAuth bool `json:"rememberAuth"`
InUsed bool `json:"inUsed"`
EntryID uint `json:"entryID"` EntryID uint `json:"entryID"`
DeletedAt time.Time `json:"deletedAt"`
} }

View File

@ -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"`
}

8
core/app/model/group.go Normal file
View File

@ -0,0 +1,8 @@
package model
type Group struct {
BaseModel
IsDefault bool `json:"isDefault"`
Name string `json:"name"`
Type string `json:"type"`
}

View File

@ -1,8 +1,8 @@
package repo package repo
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/core/global"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -14,15 +14,10 @@ type ICommandRepo interface {
WithByInfo(info string) DBOption WithByInfo(info string) DBOption
Create(command *model.Command) error Create(command *model.Command) error
Update(id uint, vars map[string]interface{}) error Update(id uint, vars map[string]interface{}) error
UpdateGroup(group, newGroup uint) error
Delete(opts ...DBOption) error Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Command, error) Get(opts ...DBOption) (model.Command, error)
WithLikeName(name string) DBOption 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 { func NewICommandRepo() ICommandRepo {
@ -39,16 +34,6 @@ func (u *CommandRepo) Get(opts ...DBOption) (model.Command, error) {
return command, err 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) { func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Command, error) {
var users []model.Command var users []model.Command
db := global.DB.Model(&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 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) { func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
var commands []model.Command var commands []model.Command
db := global.DB.Model(&model.Command{}) db := global.DB.Model(&model.Command{})
@ -83,16 +56,6 @@ func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
return commands, err 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 { func (c *CommandRepo) WithByInfo(info string) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
if len(info) == 0 { if len(info) == 0 {
@ -107,13 +70,12 @@ func (u *CommandRepo) Create(command *model.Command) error {
return global.DB.Create(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 { func (u *CommandRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Command{}).Where("id = ?", id).Updates(vars).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 { func (u *CommandRepo) Delete(opts ...DBOption) error {
db := global.DB db := global.DB
@ -123,14 +85,6 @@ func (u *CommandRepo) Delete(opts ...DBOption) error {
return db.Delete(&model.Command{}).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 { func (a CommandRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 { if len(name) == 0 {

View File

@ -1,6 +1,9 @@
package repo package repo
import ( import (
"fmt"
"github.com/1Panel-dev/1Panel/core/constant"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -12,6 +15,8 @@ type ICommonRepo interface {
WithByIDs(ids []uint) DBOption WithByIDs(ids []uint) DBOption
WithByType(ty string) DBOption WithByType(ty string) DBOption
WithOrderBy(orderStr string) DBOption WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
} }
type CommonRepo struct{} type CommonRepo struct{}
@ -51,3 +56,18 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
return g.Order(orderStr) 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))
}
}

View File

@ -1,8 +1,8 @@
package repo package repo
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/core/global"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -14,14 +14,42 @@ type IGroupRepo interface {
Create(group *model.Group) error Create(group *model.Group) error
Update(id uint, vars map[string]interface{}) error Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) 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 CancelDefault(groupType string) error
WithByHostDefault() DBOption
} }
func NewIGroupRepo() IGroupRepo { func NewIGroupRepo() IGroupRepo {
return &GroupRepo{} 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) { func (u *GroupRepo) Get(opts ...DBOption) (model.Group, error) {
var group model.Group var group model.Group
db := global.DB 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 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 { func (u *GroupRepo) Delete(opts ...DBOption) error {
db := global.DB db := global.DB
for _, opt := range opts { for _, opt := range opts {
@ -65,5 +87,7 @@ func (u *GroupRepo) Delete(opts ...DBOption) error {
} }
func (u *GroupRepo) CancelDefault(groupType string) 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
} }

113
core/app/repo/host.go Normal file
View File

@ -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
}

View File

@ -5,6 +5,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
@ -19,6 +20,8 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client" "github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
"github.com/1Panel-dev/1Panel/core/utils/encrypt" "github.com/1Panel-dev/1Panel/core/utils/encrypt"
fileUtils "github.com/1Panel-dev/1Panel/core/utils/files" 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/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
@ -271,12 +274,17 @@ func (u *BackupService) Delete(id uint) error {
if backup.Type == constant.Local { if backup.Type == constant.Local {
return buserr.New(constant.ErrBackupLocalDelete) return buserr.New(constant.ErrBackupLocalDelete)
} }
if backup.InUsed {
return buserr.New(constant.ErrBackupInUsed)
}
if backup.Type == constant.OneDrive { if backup.Type == constant.OneDrive {
global.Cron.Remove(cron.EntryID(backup.EntryID)) 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)) return backupRepo.Delete(commonRepo.WithByID(id))
} }

128
core/app/service/command.go Normal file
View File

@ -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)
}

View File

@ -3,8 +3,11 @@ package service
import "github.com/1Panel-dev/1Panel/core/app/repo" import "github.com/1Panel-dev/1Panel/core/app/repo"
var ( var (
hostRepo = repo.NewIHostRepo()
commandRepo = repo.NewICommandRepo()
commonRepo = repo.NewICommonRepo() commonRepo = repo.NewICommonRepo()
settingRepo = repo.NewISettingRepo() settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo() backupRepo = repo.NewIBackupRepo()
logRepo = repo.NewILogRepo() logRepo = repo.NewILogRepo()
groupRepo = repo.NewIGroupRepo()
) )

View File

@ -1,9 +1,17 @@
package service package service
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/dto" "bytes"
"github.com/1Panel-dev/1Panel/agent/buserr" "fmt"
"github.com/1Panel-dev/1Panel/agent/constant" "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/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -11,7 +19,7 @@ import (
type GroupService struct{} type GroupService struct{}
type IGroupService interface { type IGroupService interface {
List(req dto.GroupSearch) ([]dto.GroupInfo, error) List(req dto.OperateByType) ([]dto.GroupInfo, error)
Create(req dto.GroupCreate) error Create(req dto.GroupCreate) error
Update(req dto.GroupUpdate) error Update(req dto.GroupUpdate) error
Delete(id uint) error Delete(id uint) error
@ -21,8 +29,17 @@ func NewIGroupService() IGroupService {
return &GroupService{} return &GroupService{}
} }
func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) { func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type), commonRepo.WithOrderBy("is_default desc"), commonRepo.WithOrderBy("created_at desc")) 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 { if err != nil {
return nil, constant.ErrRecordNotFound return nil, constant.ErrRecordNotFound
} }
@ -56,22 +73,33 @@ func (u *GroupService) Delete(id uint) error {
if group.ID == 0 { if group.ID == 0 {
return constant.ErrRecordNotFound 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 { 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": case "host":
hosts, _ := hostRepo.GetList(commonRepo.WithByGroupID(id)) err = hostRepo.UpdateGroup(id, defaultGroup.ID)
if len(hosts) > 0 { case "command":
return buserr.New(constant.ErrGroupIsUsed) 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)) return groupRepo.Delete(commonRepo.WithByID(id))
} }

View File

@ -4,11 +4,12 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt" "github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/agent/utils/ssh" "github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -149,8 +150,15 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
return &host, err return &host, err
} }
func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) { func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interface{}, error) {
total, hosts, err := hostRepo.Page(search.Page, search.PageSize, hostRepo.WithByInfo(search.Info), commonRepo.WithByGroupID(search.GroupID)) 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 { if err != nil {
return 0, nil, err 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()) return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
if req.GroupID == 0 { if req.GroupID == 0 {
group, err := groupRepo.Get(groupRepo.WithByHostDefault()) group, err := groupRepo.Get(commonRepo.WithByType("host"), groupRepo.WithByDefault(true))
if err != nil { if err != nil {
return nil, errors.New("get default group failed") 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 { func (u *HostService) Delete(ids []uint) error {
hosts, _ := hostRepo.GetList(commonRepo.WithIdsIn(ids)) hosts, _ := hostRepo.GetList(commonRepo.WithByIDs(ids))
for _, host := range hosts { for _, host := range hosts {
if host.ID == 0 { if host.ID == 0 {
return constant.ErrRecordNotFound 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 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 { func (u *HostService) Update(id uint, upMap map[string]interface{}) error {

View File

@ -37,6 +37,7 @@ var (
ErrPortInUsed = "ErrPortInUsed" ErrPortInUsed = "ErrPortInUsed"
ErrCmdTimeout = "ErrCmdTimeout" ErrCmdTimeout = "ErrCmdTimeout"
ErrGroupIsUsed = "ErrGroupIsUsed" ErrGroupIsUsed = "ErrGroupIsUsed"
ErrGroupIsDefault = "ErrGroupIsDefault"
) )
// api // api

View File

@ -14,6 +14,7 @@ ErrProxy: "请求错误,请检查该节点状态: {{ .detail }}"
ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!" ErrCmdTimeout: "命令执行超时!"
ErrEntrance: "安全入口信息错误,请检查后重试!" ErrEntrance: "安全入口信息错误,请检查后重试!"
ErrGroupIsDefault: '默认分组,无法删除'
ErrGroupIsUsed: "分组正在使用中,无法删除" ErrGroupIsUsed: "分组正在使用中,无法删除"
ErrLocalDelete: "无法删除本地节点!" ErrLocalDelete: "无法删除本地节点!"
ErrMasterAddr: "当前未设置主节点地址,无法添加节点!" ErrMasterAddr: "当前未设置主节点地址,无法添加节点!"

View File

@ -13,6 +13,7 @@ func Init() {
migrations.InitSetting, migrations.InitSetting,
migrations.InitOneDrive, migrations.InitOneDrive,
migrations.InitMasterAddr, migrations.InitMasterAddr,
migrations.InitHost,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -15,13 +15,16 @@ import (
) )
var AddTable = &gormigrate.Migration{ var AddTable = &gormigrate.Migration{
ID: "20240808-add-table", ID: "20240819-add-table",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate( return tx.AutoMigrate(
&model.OperationLog{}, &model.OperationLog{},
&model.LoginLog{}, &model.LoginLog{},
&model.Setting{}, &model.Setting{},
&model.BackupAccount{}, &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{ var InitOneDrive = &gormigrate.Migration{
ID: "20240808-init-one-drive", ID: "20240808-init-one-drive",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {

21
core/router/command.go Normal file
View File

@ -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)
}
}

View File

@ -6,5 +6,8 @@ func commonGroups() []CommonRouter {
&BackupRouter{}, &BackupRouter{},
&LogRouter{}, &LogRouter{},
&SettingRouter{}, &SettingRouter{},
&CommandRouter{},
&HostRouter{},
&GroupRouter{},
} }
} }

View File

@ -1,14 +1,14 @@
package router package router
import ( 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" "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") groupRouter := Router.Group("groups")
baseApi := v2.ApiGroupApp.BaseApi baseApi := v2.ApiGroupApp.BaseApi

25
core/router/ro_host.go Normal file
View File

@ -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)
}
}

View File

@ -5,7 +5,6 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/base32"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
@ -31,7 +30,7 @@ func StringDecryptWithBase64(text string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return base32.StdEncoding.EncodeToString([]byte(decryptItem)), nil return base64.StdEncoding.EncodeToString([]byte(decryptItem)), nil
} }
func StringEncrypt(text string) (string, error) { func StringEncrypt(text string) (string, error) {

69
core/utils/http/new.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -9,3 +9,11 @@ import (
func Proxy(c *gin.Context, currentNode string) error { func Proxy(c *gin.Context, currentNode string) error {
return nil return nil
} }
func UpdateGroup(name string, group, newGroup uint) error {
return nil
}
func CheckBackupUsed(id uint) error {
return nil
}

View File

@ -1,19 +1,16 @@
export namespace Command { export namespace Command {
export interface CommandInfo { export interface CommandInfo {
id: number; id: number;
type: string;
name: string; name: string;
groupID: number; groupID: number;
command: string; command: string;
} }
export interface CommandOperate { export interface CommandOperate {
id: number; id: number;
type: string;
name: string; name: string;
groupID: number; groupID: number;
command: string; command: string;
} }
export interface RedisCommand {
id: number;
name: string;
command: string;
}
} }

View File

@ -4,6 +4,7 @@ export namespace Group {
name: string; name: string;
type: string; type: string;
isDefault: boolean; isDefault: boolean;
isDelete: boolean;
} }
export interface GroupCreate { export interface GroupCreate {
id: number; id: number;
@ -15,7 +16,4 @@ export namespace Group {
name: string; name: string;
isDefault: boolean; isDefault: boolean;
} }
export interface GroupSearch {
type: string;
}
} }

View File

@ -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<string>(`/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<ResPage<Backup.RecordInfo>>(`/backups/record/search`, params, TimeoutEnum.T_5M);
};
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M);
};
export const getFilesFromBackup = (type: string) => {
return http.post<Array<any>>(`/backups/search/files`, { type: type });
};
// backup-core
export const refreshOneDrive = () => {
return http.post(`/core/backup/refresh/onedrive`, {});
};
export const getBackupList = () => {
return http.get<Array<Backup.BackupOption>>(`/core/backup/options`);
};
export const getLocalBackupDir = () => {
return http.get<string>(`/core/backup/local`);
};
export const searchBackup = (params: Backup.SearchWithType) => {
return http.post<ResPage<Backup.BackupInfo>>(`/core/backup/search`, params);
};
export const getOneDriveInfo = () => {
return http.get<Backup.OneDriveInfo>(`/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<Backup.BackupOperate>(`/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);
};

View File

@ -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<Array<Command.CommandInfo>>(`/core/commands/list`, { type: type });
};
export const getCommandPage = (params: SearchWithPage) => {
return http.post<ResPage<Command.CommandInfo>>(`/core/commands/search`, params);
};
export const getCommandTree = (type: string) => {
return http.post<any>(`/core/commands/tree`, { type: type });
};
export const addCommand = (params: Command.CommandOperate) => {
return http.post<Command.CommandOperate>(`/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);
};

View File

@ -1,15 +1,15 @@
import { Group } from '../interface/group'; import { Group } from '../interface/group';
import http from '@/api'; import http from '@/api';
export const GetGroupList = (params: Group.GroupSearch) => { export const GetGroupList = (type: string) => {
return http.post<Array<Group.GroupInfo>>(`/groups/search`, params); return http.post<Array<Group.GroupInfo>>(`/core/groups/search`, { type: type });
}; };
export const CreateGroup = (params: Group.GroupCreate) => { export const CreateGroup = (params: Group.GroupCreate) => {
return http.post<Group.GroupCreate>(`/groups`, params); return http.post<Group.GroupCreate>(`/core/groups`, params);
}; };
export const UpdateGroup = (params: Group.GroupUpdate) => { export const UpdateGroup = (params: Group.GroupUpdate) => {
return http.post(`/groups/update`, params); return http.post(`/core/groups/update`, params);
}; };
export const DeleteGroup = (id: number) => { export const DeleteGroup = (id: number) => {
return http.post(`/groups/del`, { id: id }); return http.post(`/core/groups/del`, { id: id });
}; };

View File

@ -1,90 +1,8 @@
import http from '@/api'; import http from '@/api';
import { ResPage, SearchWithPage } from '../interface'; import { ResPage } from '../interface';
import { Command } from '../interface/command';
import { Host } from '../interface/host'; import { Host } from '../interface/host';
import { Base64 } from 'js-base64';
import { deepCopy } from '@/utils/util';
import { TimeoutEnum } from '@/enums/http-enum'; import { TimeoutEnum } from '@/enums/http-enum';
export const searchHosts = (params: Host.SearchWithPage) => {
return http.post<ResPage<Host.Host>>(`/hosts/search`, params);
};
export const getHostTree = (params: Host.ReqSearch) => {
return http.post<Array<Host.HostTree>>(`/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<Host.HostOperate>(`/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<boolean>(`/hosts/test/byinfo`, request);
};
export const testByID = (id: number) => {
return http.post<boolean>(`/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<Array<Command.CommandInfo>>(`/hosts/command`, {});
};
export const getCommandPage = (params: SearchWithPage) => {
return http.post<ResPage<Command.CommandInfo>>(`/hosts/command/search`, params);
};
export const getCommandTree = () => {
return http.get<any>(`/hosts/command/tree`);
};
export const addCommand = (params: Command.CommandOperate) => {
return http.post<Command.CommandOperate>(`/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<Array<Command.RedisCommand>>(`/hosts/command/redis`, {});
};
export const getRedisCommandPage = (params: SearchWithPage) => {
return http.post<ResPage<Command.RedisCommand>>(`/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 // firewall
export const loadFireBaseInfo = () => { export const loadFireBaseInfo = () => {
return http.get<Host.FirewallBase>(`/hosts/firewall/base`); return http.get<Host.FirewallBase>(`/hosts/firewall/base`);

View File

@ -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<ResPage<Host.Host>>(`/core/hosts/search`, params);
};
export const getHostTree = (params: Host.ReqSearch) => {
return http.post<Array<Host.HostTree>>(`/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<Host.HostOperate>(`/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<boolean>(`/core/hosts/test/byinfo`, request);
};
export const testByID = (id: number) => {
return http.post<boolean>(`/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);
};

View File

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: "panel"; /* Project id 3575356 */ font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1717570629440') format('woff2'), src: url('iconfont.woff2?t=1723798525783') format('woff2'),
url('iconfont.woff?t=1717570629440') format('woff'), url('iconfont.woff?t=1723798525783') format('woff'),
url('iconfont.ttf?t=1717570629440') format('truetype'), url('iconfont.ttf?t=1723798525783') format('truetype'),
url('iconfont.svg?t=1717570629440#panel') format('svg'); url('iconfont.svg?t=1723798525783#panel') format('svg');
} }
.panel { .panel {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.p-zhongduan:before {
content: "\e731";
}
.p-docker1:before { .p-docker1:before {
content: "\e76a"; content: "\e76a";
} }
@ -178,18 +182,10 @@
content: "\e856"; content: "\e856";
} }
.p-webdav:before {
content: "\e622";
}
.p-xiangqing:before { .p-xiangqing:before {
content: "\e677"; content: "\e677";
} }
.p-onedrive:before {
content: "\e601";
}
.p-caidan:before { .p-caidan:before {
content: "\e61d"; content: "\e61d";
} }
@ -198,14 +194,6 @@
content: "\e744"; content: "\e744";
} }
.p-tengxunyun1:before {
content: "\e651";
}
.p-qiniuyun:before {
content: "\e62c";
}
.p-file-png:before { .p-file-png:before {
content: "\e7ae"; content: "\e7ae";
} }
@ -250,10 +238,6 @@
content: "\e60f"; content: "\e60f";
} }
.p-aws:before {
content: "\e600";
}
.p-taolun:before { .p-taolun:before {
content: "\e602"; content: "\e602";
} }
@ -262,22 +246,10 @@
content: "\e616"; content: "\e616";
} }
.p-SFTP:before {
content: "\e647";
}
.p-huaban88:before { .p-huaban88:before {
content: "\e67c"; content: "\e67c";
} }
.p-oss:before {
content: "\e607";
}
.p-minio:before {
content: "\e63c";
}
.p-logout:before { .p-logout:before {
content: "\e8fe"; content: "\e8fe";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "p-", "css_prefix_text": "p-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "30053592",
"name": "终端",
"font_class": "zhongduan",
"unicode": "e731",
"unicode_decimal": 59185
},
{ {
"icon_id": "1064806", "icon_id": "1064806",
"name": "docker", "name": "docker",
@ -292,13 +299,6 @@
"unicode": "e856", "unicode": "e856",
"unicode_decimal": 59478 "unicode_decimal": 59478
}, },
{
"icon_id": "23044673",
"name": "webdav",
"font_class": "webdav",
"unicode": "e622",
"unicode_decimal": 58914
},
{ {
"icon_id": "10293150", "icon_id": "10293150",
"name": "详情", "name": "详情",
@ -306,13 +306,6 @@
"unicode": "e677", "unicode": "e677",
"unicode_decimal": 58999 "unicode_decimal": 58999
}, },
{
"icon_id": "13015332",
"name": "onedrive",
"font_class": "onedrive",
"unicode": "e601",
"unicode_decimal": 58881
},
{ {
"icon_id": "7708032", "icon_id": "7708032",
"name": "菜单", "name": "菜单",
@ -327,20 +320,6 @@
"unicode": "e744", "unicode": "e744",
"unicode_decimal": 59204 "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", "icon_id": "19671162",
"name": "png-1", "name": "png-1",
@ -418,13 +397,6 @@
"unicode": "e60f", "unicode": "e60f",
"unicode_decimal": 58895 "unicode_decimal": 58895
}, },
{
"icon_id": "32101973",
"name": "Amazon_Web_Services_Logo",
"font_class": "aws",
"unicode": "e600",
"unicode_decimal": 58880
},
{ {
"icon_id": "1760690", "icon_id": "1760690",
"name": "讨论", "name": "讨论",
@ -439,13 +411,6 @@
"unicode": "e616", "unicode": "e616",
"unicode_decimal": 58902 "unicode_decimal": 58902
}, },
{
"icon_id": "13532955",
"name": "SFTP",
"font_class": "SFTP",
"unicode": "e647",
"unicode_decimal": 58951
},
{ {
"icon_id": "15337722", "icon_id": "15337722",
"name": "Logo GitHub", "name": "Logo GitHub",
@ -453,20 +418,6 @@
"unicode": "e67c", "unicode": "e67c",
"unicode_decimal": 59004 "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", "icon_id": "924436",
"name": "logout", "name": "logout",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -72,7 +72,7 @@ const rules = reactive({
}); });
const loadGroups = async (groupName: string) => { const loadGroups = async (groupName: string) => {
const res = await GetGroupList({ type: dialogData.value.groupType }); const res = await GetGroupList(dialogData.value.groupType);
groupList.value = res.data; groupList.value = res.data;
for (const group of groupList.value) { for (const group of groupList.value) {
if (group.name === groupName) { if (group.name === groupName) {

View File

@ -21,7 +21,13 @@
{{ $t('commons.table.default') }} {{ $t('commons.table.default') }}
</span> </span>
<span v-if="row.name !== 'default'">{{ row.name }}</span> <span v-if="row.name !== 'default'">{{ row.name }}</span>
<span v-if="row.isDefault">({{ $t('commons.table.default') }})</span> <el-tag v-if="row.isDefault" type="success" class="ml-2" size="small">
({{ $t('commons.table.default') }})
</el-tag>
<el-tag type="warning" size="small" class="ml-4" v-if="row.isDelete">
{{ $t('app.takeDown') }}
</el-tag>
</div> </div>
<el-form @submit.prevent ref="groupForm" v-if="row.edit" :model="row"> <el-form @submit.prevent ref="groupForm" v-if="row.edit" :model="row">
@ -31,6 +37,24 @@
</el-form> </el-form>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status" :min-width="60">
<template #default="{ row }">
<el-button
v-if="row.status === 'Enable'"
@click="onChangeStatus(row.id, 'disable')"
link
icon="VideoPlay"
type="success"
>
{{ $t('commons.status.enabled') }}
</el-button>
<el-button v-else icon="VideoPause" link type="danger" @click="onChangeStatus(row.id, 'enable')">
{{ $t('commons.status.disabled') }}
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.operate')"> <el-table-column :label="$t('commons.table.operate')">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<div> <div>
@ -52,7 +76,12 @@
<el-button link v-if="row.edit" type="primary" @click="search()"> <el-button link v-if="row.edit" type="primary" @click="search()">
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </el-button>
<el-button link v-if="!row.edit && !row.isDefault" type="primary" @click="setDefault(row)"> <el-button
link
v-if="!row.edit && !row.isDefault && !row.isDelete"
type="primary"
@click="setDefault(row)"
>
{{ $t('website.setDefault') }} {{ $t('website.setDefault') }}
</el-button> </el-button>
</div> </div>
@ -92,17 +121,8 @@ const acceptParams = (params: DialogProps): void => {
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
const search = () => { const search = () => {
data.value = []; GetGroupList(type.value).then((res) => {
GetGroupList({ type: type.value }).then((res) => { data.value = res.data || [];
for (const d of res.data) {
const g = {
id: d.id,
name: d.name,
isDefault: d.isDefault,
edit: false,
};
data.value.push(g);
}
}); });
}; };
@ -136,6 +156,18 @@ const setDefault = (group: Group.GroupInfo) => {
}); });
}; };
const onChangeStatus = async (row: any, status: string) => {
ElMessageBox.confirm(i18n.global.t('cronjob.' + status + 'Msg'), i18n.global.t('cronjob.changeStatus'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => {
row.status = status === 'enable' ? 'Enable' : 'Disable';
await UpdateGroup(row);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
});
};
const openCreate = () => { const openCreate = () => {
for (const d of data.value) { for (const d of data.value) {
if (d.name == '') { if (d.name == '') {
@ -150,6 +182,7 @@ const openCreate = () => {
name: '', name: '',
isDefault: false, isDefault: false,
edit: true, edit: true,
status: 'Enable',
}; };
data.value.unshift(g); data.value.unshift(g);
}; };

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant'; import { Layout } from '@/routers/constant';
const cronRouter = { const cronRouter = {
sort: 8, sort: 9,
path: '/cronjobs', path: '/cronjobs',
component: Layout, component: Layout,
redirect: '/cronjobs', redirect: '/cronjobs',

View File

@ -40,17 +40,6 @@ const hostRouter = {
requiresAuth: false, requiresAuth: false,
}, },
}, },
{
path: '/hosts/terminal',
name: 'Terminal',
props: true,
component: () => import('@/views/host/terminal/index.vue'),
meta: {
title: 'menu.terminal',
keepAlive: true,
requiresAuth: false,
},
},
{ {
path: '/hosts/firewall/port', path: '/hosts/firewall/port',
name: 'FirewallPort', name: 'FirewallPort',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant'; import { Layout } from '@/routers/constant';
const logsRouter = { const logsRouter = {
sort: 8, sort: 11,
path: '/logs', path: '/logs',
component: Layout, component: Layout,
redirect: '/logs/operation', redirect: '/logs/operation',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant'; import { Layout } from '@/routers/constant';
const settingRouter = { const settingRouter = {
sort: 10, sort: 12,
path: '/settings', path: '/settings',
component: Layout, component: Layout,
redirect: '/settings/panel', redirect: '/settings/panel',

View File

@ -0,0 +1,26 @@
import { Layout } from '@/routers/constant';
const terminalRouter = {
sort: 8,
path: '/terminal',
component: Layout,
redirect: '/terminal',
meta: {
icon: 'p-zhongduan',
title: 'menu.terminal',
},
children: [
{
path: '/terminal',
name: 'Terminal',
props: true,
component: () => import('@/views/terminal/index.vue'),
meta: {
keepAlive: true,
requiresAuth: false,
},
},
],
};
export default terminalRouter;

View File

@ -27,7 +27,7 @@
<el-table-column min-width="40"> <el-table-column min-width="40">
<template #default="scope"> <template #default="scope">
<el-button <el-button
v-if="scope.row.lineStatus === 'create'" v-if="scope.row.lineStatus === 'create' || scope.row.lineStatus === 'edit'"
link link
type="primary" type="primary"
@click="handleCmdSave(scope.row)" @click="handleCmdSave(scope.row)"
@ -38,15 +38,20 @@
v-if="!scope.row.lineStatus || scope.row.lineStatus === 'saved'" v-if="!scope.row.lineStatus || scope.row.lineStatus === 'saved'"
link link
type="primary" type="primary"
@click="scope.row.lineStatus = 'create'" @click="scope.row.lineStatus = 'edit'"
> >
{{ $t('commons.button.edit') }} {{ $t('commons.button.edit') }}
</el-button> </el-button>
<el-button v-if="scope.row.lineStatus === 'create'" link type="primary" @click="search()"> <el-button
v-if="scope.row.lineStatus === 'create' || scope.row.lineStatus === 'edit'"
link
type="primary"
@click="search()"
>
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </el-button>
<el-button <el-button
v-if="scope.row.lineStatus !== 'create'" v-if="scope.row.lineStatus !== 'create' && scope.row.lineStatus !== 'edit'"
link link
type="primary" type="primary"
@click="handleCmdDelete(scope.$index)" @click="handleCmdDelete(scope.$index)"
@ -68,7 +73,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Command } from '@/api/interface/command'; import { Command } from '@/api/interface/command';
import { saveRedisCommand, deleteRedisCommand, getRedisCommandPage } from '@/api/modules/host'; import { deleteCommand, getCommandPage, addCommand, editCommand } from '@/api/modules/command';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
@ -81,6 +86,8 @@ const paginationConfig = reactive({
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 10,
total: 0, total: 0,
orderBy: 'name',
order: 'ascending',
}); });
const opRef = ref(); const opRef = ref();
@ -99,6 +106,7 @@ const handleCmdAdd = () => {
let item = { let item = {
name: '', name: '',
command: '', command: '',
type: 'redis',
lineStatus: 'create', lineStatus: 'create',
}; };
data.value.push(item); data.value.push(item);
@ -113,7 +121,8 @@ const handleCmdSave = async (row: any) => {
return; return;
} }
loading.value = true; loading.value = true;
await saveRedisCommand(row) if (row.lineStatus === 'create') {
await addCommand(row)
.then(() => { .then(() => {
loading.value = false; loading.value = false;
row.lineStatus = 'saved'; row.lineStatus = 'saved';
@ -124,9 +133,20 @@ const handleCmdSave = async (row: any) => {
loading.value = false; loading.value = false;
}); });
return; return;
}
await editCommand(row)
.then(() => {
loading.value = false;
row.lineStatus = 'saved';
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
}; };
const batchDelete = async (row: Command.RedisCommand | null) => { const batchDelete = async (row: Command.CommandInfo | null) => {
let names = []; let names = [];
let ids = []; let ids = [];
if (row) { if (row) {
@ -145,19 +165,24 @@ const batchDelete = async (row: Command.RedisCommand | null) => {
i18n.global.t('terminal.quickCommand'), i18n.global.t('terminal.quickCommand'),
i18n.global.t('commons.button.delete'), i18n.global.t('commons.button.delete'),
]), ]),
api: deleteRedisCommand, api: deleteCommand,
params: { ids: ids }, params: { ids: ids },
}); });
}; };
const search = async () => { const search = async (column?: any) => {
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
let params = { let params = {
page: paginationConfig.currentPage, page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize, pageSize: paginationConfig.pageSize,
orderBy: paginationConfig.orderBy,
order: paginationConfig.order,
info: '', info: '',
type: 'redis',
}; };
loading.value = true; loading.value = true;
await getRedisCommandPage(params) await getCommandPage(params)
.then((res) => { .then((res) => {
loading.value = false; loading.value = false;
data.value = res.data.items || []; data.value = res.data.items || [];

View File

@ -152,7 +152,7 @@ import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/dat
import { Database } from '@/api/interface/database'; import { Database } from '@/api/interface/database';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang'; import i18n from '@/lang';
import { getRedisCommandList } from '@/api/modules/host'; import { getCommandList } from '@/api/modules/command';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const loading = ref(false); const loading = ref(false);
@ -364,7 +364,7 @@ const installCli = async () => {
}; };
const loadQuickCmd = async () => { const loadQuickCmd = async () => {
const res = await getRedisCommandList(); const res = await getCommandList('redis');
quickCmdList.value = res.data || []; quickCmdList.value = res.data || [];
}; };

View File

@ -23,7 +23,7 @@
<el-option :value="item.id" :label="item.name" /> <el-option :value="item.id" :label="item.name" />
</div> </div>
</el-select> </el-select>
<TableSearch @search="search()" v-model:searchName="commandReq.name" /> <TableSearch @search="search()" v-model:searchName="info" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
@ -109,7 +109,7 @@
import { Command } from '@/api/interface/command'; import { Command } from '@/api/interface/command';
import GroupDialog from '@/components/group/index.vue'; import GroupDialog from '@/components/group/index.vue';
import GroupChangeDialog from '@/components/group/change.vue'; import GroupChangeDialog from '@/components/group/change.vue';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/host'; import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/command';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import type { ElForm } from 'element-plus'; import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
@ -151,19 +151,16 @@ const acceptParams = () => {
const defaultGroupID = ref(); const defaultGroupID = ref();
let commandInfo = reactive<Command.CommandOperate>({ let commandInfo = reactive<Command.CommandOperate>({
id: 0, id: 0,
type: 'command',
name: '', name: '',
groupID: 0, groupID: 0,
command: '', command: '',
}); });
const commandReq = reactive({
name: '',
});
const cmdVisible = ref<boolean>(false); const cmdVisible = ref<boolean>(false);
const loadGroups = async () => { const loadGroups = async () => {
const res = await GetGroupList({ type: 'command' }); const res = await GetGroupList('command');
groupList.value = res.data; groupList.value = res.data;
for (const group of groupList.value) { for (const group of groupList.value) {
if (group.isDefault) { if (group.isDefault) {
@ -274,7 +271,7 @@ const search = async (column?: any) => {
info: info.value, info: info.value,
orderBy: paginationConfig.orderBy, orderBy: paginationConfig.orderBy,
order: paginationConfig.order, order: paginationConfig.order,
name: commandReq.name, type: 'command',
}; };
loading.value = true; loading.value = true;
await getCommandPage(params) await getCommandPage(params)

View File

@ -31,7 +31,7 @@
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus'; import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { editHostGroup } from '@/api/modules/host'; import { editHostGroup } from '@/api/modules/terminal';
import { GetGroupList } from '@/api/modules/group'; import { GetGroupList } from '@/api/modules/group';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
@ -66,7 +66,7 @@ const rules = reactive({
}); });
const loadGroups = async (groupName: string) => { const loadGroups = async (groupName: string) => {
const res = await GetGroupList({ type: 'host' }); const res = await GetGroupList('host');
groupList.value = res.data; groupList.value = res.data;
for (const group of groupList.value) { for (const group of groupList.value) {
if (group.name === groupName) { if (group.name === groupName) {

View File

@ -60,8 +60,8 @@
<script setup lang="ts"> <script setup lang="ts">
import GroupDialog from '@/components/group/index.vue'; import GroupDialog from '@/components/group/index.vue';
import GroupChangeDialog from '@/components/group/change.vue'; import GroupChangeDialog from '@/components/group/change.vue';
import OperateDialog from '@/views/host/terminal/host/operate/index.vue'; import OperateDialog from '@/views/terminal/host/operate/index.vue';
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/host'; import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/terminal';
import { GetGroupList } from '@/api/modules/group'; import { GetGroupList } from '@/api/modules/group';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
@ -138,7 +138,7 @@ const onBatchDelete = async (row: Host.Host | null) => {
}; };
const loadGroups = async () => { const loadGroups = async () => {
const res = await GetGroupList({ type: 'host' }); const res = await GetGroupList('host');
groupList.value = res.data; groupList.value = res.data;
}; };

View File

@ -81,7 +81,7 @@
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus'; import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { addHost, editHost, testByInfo } from '@/api/modules/host'; import { addHost, editHost, testByInfo } from '@/api/modules/terminal';
import { GetGroupList } from '@/api/modules/group'; import { GetGroupList } from '@/api/modules/group';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
@ -122,7 +122,7 @@ const rules = reactive({
}); });
const loadGroups = async () => { const loadGroups = async () => {
const res = await GetGroupList({ type: 'host' }); const res = await GetGroupList('host');
groupList.value = res.data; groupList.value = res.data;
if (dialogData.value.title === 'create') { if (dialogData.value.title === 'create') {
for (const item of groupList.value) { for (const item of groupList.value) {

View File

@ -27,9 +27,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import HostTab from '@/views/host/terminal/host/index.vue'; import HostTab from '@/views/terminal/host/index.vue';
import CommandTab from '@/views/host/terminal/command/index.vue'; import CommandTab from '@/views/terminal/command/index.vue';
import TerminalTab from '@/views/host/terminal/terminal/index.vue'; import TerminalTab from '@/views/terminal/terminal/index.vue';
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
const activeNames = ref<string>('terminal'); const activeNames = ref<string>('terminal');

View File

@ -68,7 +68,7 @@
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
import { Host } from '@/api/interface/host'; import { Host } from '@/api/interface/host';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { addHost, testByInfo } from '@/api/modules/host'; import { addHost, testByInfo } from '@/api/modules/terminal';
import i18n from '@/lang'; import i18n from '@/lang';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';

View File

@ -143,15 +143,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, getCurrentInstance, watch, nextTick, computed, onMounted } from 'vue'; import { ref, getCurrentInstance, watch, nextTick, computed, onMounted } from 'vue';
import Terminal from '@/components/terminal/index.vue'; import Terminal from '@/components/terminal/index.vue';
import HostDialog from '@/views/host/terminal/terminal/host-create.vue'; import HostDialog from '@/views/terminal/terminal/host-create.vue';
import type Node from 'element-plus/es/components/tree/src/model/node'; import type Node from 'element-plus/es/components/tree/src/model/node';
import { ElTree } from 'element-plus'; import { ElTree } from 'element-plus';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import i18n from '@/lang'; import i18n from '@/lang';
import { Host } from '@/api/interface/host'; import { Host } from '@/api/interface/host';
import { getCommandTree, getHostTree, testByID } from '@/api/modules/host'; import { getHostTree, testByID } from '@/api/modules/terminal';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import router from '@/routers'; import router from '@/routers';
import { getCommandTree } from '@/api/modules/command';
const dialogRef = ref(); const dialogRef = ref();
const ctx = getCurrentInstance() as any; const ctx = getCurrentInstance() as any;
@ -287,7 +288,7 @@ const filterHost = (value: string, data: any) => {
return data.label.includes(value); return data.label.includes(value);
}; };
const loadCommandTree = async () => { const loadCommandTree = async () => {
const res = await getCommandTree(); const res = await getCommandTree('command');
commandTree.value = res.data || []; commandTree.value = res.data || [];
}; };
@ -348,7 +349,7 @@ const onReconnect = async (item: any) => {
nextTick(() => { nextTick(() => {
ctx.refs[`t-${item.index}`] && ctx.refs[`t-${item.index}`] &&
ctx.refs[`t-${item.index}`][0].acceptParams({ ctx.refs[`t-${item.index}`][0].acceptParams({
endpoint: '/api/v2/terminals', endpoint: '/api/v2/core/hosts/terminal',
args: `id=${item.wsID}`, args: `id=${item.wsID}`,
error: res.data ? '' : 'Failed to set up the connection. Please check the host information', error: res.data ? '' : 'Failed to set up the connection. Please check the host information',
}); });
@ -380,7 +381,7 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
nextTick(() => { nextTick(() => {
ctx.refs[`t-${terminalValue.value}`] && ctx.refs[`t-${terminalValue.value}`] &&
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({ ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
endpoint: '/api/v2/terminals', endpoint: '/api/v2/core/hosts/terminal',
args: `id=${wsID}`, args: `id=${wsID}`,
initCmd: initCmd.value, initCmd: initCmd.value,
error: res.data ? '' : 'Authentication failed. Please check the host information !', error: res.data ? '' : 'Authentication failed. Please check the host information !',

View File

@ -88,7 +88,7 @@ const submit = async (formEl: FormInstance | undefined) => {
}); });
}; };
const search = async () => { const search = async () => {
const res = await GetGroupList({ type: 'website' }); const res = await GetGroupList('website');
groups.value = res.data; groups.value = res.data;
GetWebsite(websiteId.value).then((res) => { GetWebsite(websiteId.value).then((res) => {

View File

@ -829,7 +829,7 @@ const acceptParams = async (installPath: string) => {
} }
staticPath.value = installPath + '/www/sites/'; staticPath.value = installPath + '/www/sites/';
const res = await GetGroupList({ type: 'website' }); const res = await GetGroupList('website');
groups.value = res.data; groups.value = res.data;
website.value.webSiteGroupId = res.data[0].id; website.value.webSiteGroupId = res.data[0].id;
website.value.type = 'deployment'; website.value.type = 'deployment';

View File

@ -334,7 +334,7 @@ const search = async () => {
}; };
const listGroup = async () => { const listGroup = async () => {
const res = await GetGroupList({ type: 'website' }); const res = await GetGroupList('website');
groups.value = res.data; groups.value = res.data;
}; };

2
go.mod
View File

@ -5,6 +5,7 @@ go 1.22.4
require ( require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go v1.55.5
github.com/creack/pty v1.1.9
github.com/dgraph-io/badger/v4 v4.2.0 github.com/dgraph-io/badger/v4 v4.2.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.0.1
@ -15,6 +16,7 @@ require (
github.com/goh-chunlin/go-onedrive v1.1.1 github.com/goh-chunlin/go-onedrive v1.1.1
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jinzhu/copier v0.4.0 github.com/jinzhu/copier v0.4.0
github.com/minio/minio-go/v7 v7.0.74 github.com/minio/minio-go/v7 v7.0.74
github.com/mojocn/base64Captcha v1.3.6 github.com/mojocn/base64Captcha v1.3.6

3
go.sum
View File

@ -68,6 +68,7 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -218,6 +219,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=