feat: 提取分组及快速命令功能 (#6169)
Some checks failed
sync2gitee / repo-sync (push) Failing after -8m3s

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"
)
func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
id, err := helper.GetIntParamByKey(c, "id")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
if err := backupService.CheckUsed(id); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Backup Account
// @Summary Page backup records
// @Description 获取备份记录列表分页

View file

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

View file

@ -12,67 +12,12 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
"github.com/1Panel-dev/1Panel/agent/utils/terminal"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
func (b *BaseApi) WsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
id, err := strconv.Atoi(c.Query("id"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
host, err := hostService.GetHostInfo(uint(id))
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &host)
connInfo.PrivateKey = []byte(host.PrivateKey)
if len(host.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
client, err := connInfo.NewClient()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
defer client.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
if wshandleError(wsConn, err) {
return
}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
if wshandleError(wsConn, err) {
return
}
}
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {

View file

@ -957,3 +957,15 @@ func (b *BaseApi) UpdateLoadBalanceFile(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
func (b *BaseApi) ChangeWebsiteGroup(c *gin.Context) {
var req dto.UpdateGroup
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.ChangeGroup(req.Group, req.NewGroup); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View file

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

View file

@ -14,3 +14,15 @@ type MonitorData struct {
Date []time.Time `json:"date"`
Value []interface{} `json:"value"`
}
type MonitorSetting struct {
MonitorStatus string `json:"monitorStatus"`
MonitorStoreDays string `json:"monitorStoreDays"`
MonitorInterval string `json:"monitorInterval"`
DefaultNetwork string `json:"defaultNetwork"`
}
type MonitorSettingUpdate struct {
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork"`
Value string `json:"value"`
}

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

View file

@ -15,6 +15,7 @@ type IFtpRepo interface {
Create(ftp *model.Ftp) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
WithLikeUser(user string) DBOption
WithByUser(user string) DBOption
}
@ -33,6 +34,12 @@ func (u *FtpRepo) Get(opts ...DBOption) (model.Ftp, error) {
}
func (h *FtpRepo) WithByUser(user string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("user = ?", user)
}
}
func (h *FtpRepo) WithLikeUser(user string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(user) == 0 {
return g

View file

@ -3,23 +3,11 @@ package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type HostRepo struct{}
type IHostRepo interface {
Get(opts ...DBOption) (model.Host, error)
GetList(opts ...DBOption) ([]model.Host, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error)
WithByInfo(info string) DBOption
WithByPort(port uint) DBOption
WithByUser(user string) DBOption
WithByAddr(addr string) DBOption
Create(host *model.Host) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
ListFirewallRecord() ([]model.Firewall, error)
SaveFirewallRecord(firewall *model.Firewall) error
@ -31,88 +19,6 @@ func NewIHostRepo() IHostRepo {
return &HostRepo{}
}
func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) {
var host model.Host
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&host).Error
return host, err
}
func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
var hosts []model.Host
db := global.DB.Model(&model.Host{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&hosts).Error
return hosts, err
}
func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) {
var users []model.Host
db := global.DB.Model(&model.Host{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (h *HostRepo) WithByInfo(info string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(info) == 0 {
return g
}
infoStr := "%" + info + "%"
return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr)
}
}
func (h *HostRepo) WithByPort(port uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("port = ?", port)
}
}
func (h *HostRepo) WithByUser(user string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("user = ?", user)
}
}
func (h *HostRepo) WithByAddr(addr string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("addr = ?", addr)
}
}
func (h *HostRepo) WithByGroup(group string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(group) == 0 {
return g
}
return g.Where("group_belong = ?", group)
}
}
func (h *HostRepo) Create(host *model.Host) error {
return global.DB.Create(host).Error
}
func (h *HostRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error
}
func (h *HostRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.Host{}).Error
}
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
var firewall model.Firewall
db := global.DB

View file

@ -31,6 +31,8 @@ type IWebsiteRepo interface {
DeleteBy(ctx context.Context, opts ...DBOption) error
Create(ctx context.Context, app *model.Website) error
DeleteAll(ctx context.Context) error
UpdateGroup(group, newGroup uint) error
}
func NewIWebsiteRepo() IWebsiteRepo {
@ -158,3 +160,7 @@ func (w *WebsiteRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
func (w *WebsiteRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1 ").Delete(&model.Website{}).Error
}
func (w *WebsiteRepo) UpdateGroup(group, newGroup uint) error {
return global.DB.Model(&model.Website{}).Where("website_group_id = ?", group).Updates(map[string]interface{}{"website_group_id": newGroup}).Error
}

View file

@ -3,17 +3,20 @@ package service
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"path"
"sort"
"strconv"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
@ -26,6 +29,8 @@ import (
type BackupService struct{}
type IBackupService interface {
CheckUsed(id uint) error
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
@ -97,6 +102,22 @@ func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchBy
return total, datas, err
}
func (u *BackupService) CheckUsed(id uint) error {
cronjobs, _ := cronjobRepo.List()
for _, job := range cronjobs {
if job.DownloadAccountID == id {
return buserr.New(constant.ErrBackupInUsed)
}
ids := strings.Split(job.SourceAccountIDs, ",")
for _, idItem := range ids {
if idItem == fmt.Sprintf("%v", id) {
return buserr.New(constant.ErrBackupInUsed)
}
}
}
return nil
}
type loadSizeHelper struct {
isOk bool
backupPath string
@ -309,7 +330,15 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
accounts[i].Credential, _ = encrypt.StringDecryptWithKey(accounts[i].Credential, setting.Value)
}
} else {
bodyItem, err := json.Marshal(ids)
var idItems []uint
for i := 0; i < len(ids); i++ {
item, _ := strconv.Atoi(ids[i])
idItems = append(idItems, uint(item))
}
operateByIDs := struct {
IDs []uint `json:"ids"`
}{IDs: idItems}
bodyItem, err := json.Marshal(operateByIDs)
if err != nil {
return nil, err
}
@ -327,6 +356,18 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
}
clientMap := make(map[string]backupClientHelper)
for _, item := range accounts {
if !global.IsMaster {
accessItem, err := base64.StdEncoding.DecodeString(item.AccessKey)
if err != nil {
return nil, err
}
item.AccessKey = string(accessItem)
secretItem, err := base64.StdEncoding.DecodeString(item.Credential)
if err != nil {
return nil, err
}
item.Credential = string(secretItem)
}
backClient, err := newClient(&item)
if err != nil {
return nil, err

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()
hostRepo = repo.NewIHostRepo()
groupRepo = repo.NewIGroupRepo()
commandRepo = repo.NewICommandRepo()
ftpRepo = repo.NewIFtpRepo()
clamRepo = repo.NewIClamRepo()
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) {
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithByUser(req.Info), commonRepo.WithOrderBy("created_at desc"))
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), commonRepo.WithOrderBy("created_at desc"))
if err != nil {
return 0, nil, err
}
@ -142,7 +142,7 @@ func (f *FtpService) Create(req dto.FtpCreate) (uint, error) {
if err != nil {
return 0, err
}
userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User))
userInDB, _ := ftpRepo.Get(ftpRepo.WithByUser(req.User))
if userInDB.ID != 0 {
return 0, constant.ErrRecordExist
}

View file

@ -181,10 +181,6 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil {
global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
}
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)

View file

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

View file

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

View file

@ -28,7 +28,6 @@ var AddTable = &gormigrate.Migration{
&model.App{},
&model.BackupRecord{},
&model.Clam{},
&model.Command{},
&model.ComposeTemplate{},
&model.Compose{},
&model.Cronjob{},
@ -39,15 +38,12 @@ var AddTable = &gormigrate.Migration{
&model.Forward{},
&model.Firewall{},
&model.Ftp{},
&model.Group{},
&model.Host{},
&model.ImageRepo{},
&model.JobRecords{},
&model.MonitorBase{},
&model.MonitorIO{},
&model.MonitorNetwork{},
&model.PHPExtensions{},
&model.RedisCommand{},
&model.Runtime{},
&model.Setting{},
&model.Snapshot{},
@ -74,25 +70,6 @@ var AddMonitorTable = &gormigrate.Migration{
},
}
var InitHost = &gormigrate.Migration{
ID: "20240722-init-host",
Migrate: func(tx *gorm.DB) error {
group := model.Group{
Name: "default", Type: "host", IsDefault: true,
}
if err := tx.Create(&group).Error; err != nil {
return err
}
host := model.Host{
Name: "localhost", Addr: "127.0.0.1", User: "root", Port: 22, AuthMode: "password", GroupID: group.ID,
}
if err := tx.Create(&host).Error; err != nil {
return err
}
return nil
},
}
var InitSetting = &gormigrate.Migration{
ID: "20240722-init-setting",
Migrate: func(tx *gorm.DB) error {
@ -228,25 +205,6 @@ var InitImageRepo = &gormigrate.Migration{
},
}
var InitDefaultGroup = &gormigrate.Migration{
ID: "20240722-init-default-group",
Migrate: func(tx *gorm.DB) error {
websiteGroup := &model.Group{
Name: "默认",
IsDefault: true,
Type: "website",
}
if err := tx.Create(websiteGroup).Error; err != nil {
return err
}
commandGroup := &model.Group{IsDefault: true, Name: "默认", Type: "command"}
if err := tx.Create(commandGroup).Error; err != nil {
return err
}
return nil
},
}
var InitDefaultCA = &gormigrate.Migration{
ID: "20240722-init-default-ca",
Migrate: func(tx *gorm.DB) error {

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{},
&FileRouter{},
&ToolboxRouter{},
&TerminalRouter{},
&CronjobRouter{},
&BackupRouter{},
&SettingRouter{},
&AppRouter{},
&WebsiteRouter{},
&WebsiteGroupRouter{},
&WebsiteDnsAccountRouter{},
&WebsiteAcmeAccountRouter{},
&WebsiteSSLRouter{},

View file

@ -11,15 +11,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter := Router.Group("hosts")
baseApi := v2.ApiGroupApp.BaseApi
{
hostRouter.POST("", baseApi.CreateHost)
hostRouter.POST("/del", baseApi.DeleteHost)
hostRouter.POST("/update", baseApi.UpdateHost)
hostRouter.POST("/update/group", baseApi.UpdateHostGroup)
hostRouter.POST("/search", baseApi.SearchHost)
hostRouter.POST("/tree", baseApi.HostTree)
hostRouter.POST("/test/byinfo", baseApi.TestByInfo)
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
@ -47,18 +38,6 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
hostRouter.GET("/command", baseApi.ListCommand)
hostRouter.POST("/command", baseApi.CreateCommand)
hostRouter.POST("/command/del", baseApi.DeleteCommand)
hostRouter.POST("/command/search", baseApi.SearchCommand)
hostRouter.GET("/command/tree", baseApi.SearchCommandTree)
hostRouter.POST("/command/update", baseApi.UpdateCommand)
hostRouter.GET("/command/redis", baseApi.ListRedisCommand)
hostRouter.POST("/command/redis", baseApi.SaveRedisCommand)
hostRouter.POST("/command/redis/search", baseApi.SearchRedisCommand)
hostRouter.POST("/command/redis/del", baseApi.DeleteRedisCommand)
hostRouter.POST("/tool", baseApi.GetToolStatus)
hostRouter.POST("/tool/init", baseApi.InitToolConfig)
hostRouter.POST("/tool/operate", baseApi.OperateTool)

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.POST("/del", baseApi.DeleteWebsite)
websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer)
websiteRouter.POST("/group/change", baseApi.ChangeWebsiteGroup)
websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain)

View file

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

View file

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

View file

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

View file

@ -1,11 +1,23 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"encoding/base64"
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/copier"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/1Panel-dev/1Panel/core/utils/terminal"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
// @Tags Host
@ -15,7 +27,7 @@ import (
// @Param request body dto.HostOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts [post]
// @Router /core/hosts [post]
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"}
func (b *BaseApi) CreateHost(c *gin.Context) {
var req dto.HostOperate
@ -38,7 +50,7 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
// @Param request body dto.HostConnTest true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/test/byinfo [post]
// @Router /core/hosts/test/byinfo [post]
func (b *BaseApi) TestByInfo(c *gin.Context) {
var req dto.HostConnTest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@ -56,15 +68,20 @@ func (b *BaseApi) TestByInfo(c *gin.Context) {
// @Param id path integer true "request"
// @Success 200 {boolean} connStatus
// @Security ApiKeyAuth
// @Router /hosts/test/byid/:id [post]
// @Router /core/hosts/test/byid/:id [post]
func (b *BaseApi) TestByID(c *gin.Context) {
id, err := helper.GetParamID(c)
idParam, ok := c.Params.Get("id")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("no such params find in request"))
return
}
intNum, err := strconv.Atoi(idParam)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
connStatus := hostService.TestLocalConn(id)
connStatus := hostService.TestLocalConn(uint(intNum))
helper.SuccessWithData(c, connStatus)
}
@ -75,10 +92,10 @@ func (b *BaseApi) TestByID(c *gin.Context) {
// @Param request body dto.SearchForTree true "request"
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/tree [post]
// @Router /core/hosts/tree [post]
func (b *BaseApi) HostTree(c *gin.Context) {
var req dto.SearchForTree
if err := helper.CheckBind(&req, c); err != nil {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
@ -98,7 +115,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/search [post]
// @Router /core/hosts/search [post]
func (b *BaseApi) SearchHost(c *gin.Context) {
var req dto.SearchHostWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@ -124,15 +141,15 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
// @Param request body dto.BatchDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/del [post]
// @Router /core/hosts/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"}
func (b *BaseApi) DeleteHost(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := hostService.Delete(req.Ids); err != nil {
if err := hostService.Delete(req.IDs); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -146,7 +163,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
// @Param request body dto.HostOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/update [post]
// @Router /core/hosts/update [post]
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机信息 [name][addr]","formatEN":"update host [name][addr]"}
func (b *BaseApi) UpdateHost(c *gin.Context) {
var req dto.HostOperate
@ -212,7 +229,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
// @Param request body dto.ChangeHostGroup true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/update/group [post]
// @Router /core/hosts/update/group [post]
// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"}
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
var req dto.ChangeHostGroup
@ -228,3 +245,84 @@ func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) WsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
id, err := strconv.Atoi(c.Query("id"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
host, err := hostService.GetHostInfo(uint(id))
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &host)
connInfo.PrivateKey = []byte(host.PrivateKey)
if len(host.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
client, err := connInfo.NewClient()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
defer client.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
if wshandleError(wsConn, err) {
return
}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
if wshandleError(wsConn, err) {
return
}
}
var upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func wshandleError(ws *websocket.Conn, err error) bool {
if err != nil {
global.LOG.Errorf("handler ws faled:, err: %v", err)
dt := time.Now().Add(time.Second)
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
wsData, err := json.Marshal(terminal.WsMsg{
Type: terminal.WsMsgCmd,
Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
})
if err != nil {
_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
} else {
_ = ws.WriteMessage(websocket.TextMessage, wsData)
}
}
return true
}
return false
}

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"`
}
type OperateByType struct {
Type string `json:"type"`
}
type OperateByID struct {
ID uint `json:"id"`
}

View file

@ -73,15 +73,3 @@ type TreeChild struct {
ID uint `json:"id"`
Label string `json:"label"`
}
type MonitorSetting struct {
MonitorStatus string `json:"monitorStatus"`
MonitorStoreDays string `json:"monitorStoreDays"`
MonitorInterval string `json:"monitorInterval"`
DefaultNetwork string `json:"defaultNetwork"`
}
type MonitorSettingUpdate struct {
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork"`
Value string `json:"value"`
}

View file

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

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
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/global"
"gorm.io/gorm"
)
@ -14,15 +14,10 @@ type ICommandRepo interface {
WithByInfo(info string) DBOption
Create(command *model.Command) error
Update(id uint, vars map[string]interface{}) error
UpdateGroup(group, newGroup uint) error
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Command, error)
WithLikeName(name string) DBOption
PageRedis(limit, offset int, opts ...DBOption) (int64, []model.RedisCommand, error)
GetRedis(opts ...DBOption) (model.RedisCommand, error)
GetRedisList(opts ...DBOption) ([]model.RedisCommand, error)
SaveRedis(command *model.RedisCommand) error
DeleteRedis(opts ...DBOption) error
}
func NewICommandRepo() ICommandRepo {
@ -39,16 +34,6 @@ func (u *CommandRepo) Get(opts ...DBOption) (model.Command, error) {
return command, err
}
func (u *CommandRepo) GetRedis(opts ...DBOption) (model.RedisCommand, error) {
var command model.RedisCommand
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&command).Error
return command, err
}
func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Command, error) {
var users []model.Command
db := global.DB.Model(&model.Command{})
@ -61,18 +46,6 @@ func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Com
return count, users, err
}
func (u *CommandRepo) PageRedis(page, size int, opts ...DBOption) (int64, []model.RedisCommand, error) {
var users []model.RedisCommand
db := global.DB.Model(&model.RedisCommand{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
var commands []model.Command
db := global.DB.Model(&model.Command{})
@ -83,16 +56,6 @@ func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
return commands, err
}
func (u *CommandRepo) GetRedisList(opts ...DBOption) ([]model.RedisCommand, error) {
var commands []model.RedisCommand
db := global.DB.Model(&model.RedisCommand{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&commands).Error
return commands, err
}
func (c *CommandRepo) WithByInfo(info string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(info) == 0 {
@ -107,13 +70,12 @@ func (u *CommandRepo) Create(command *model.Command) error {
return global.DB.Create(command).Error
}
func (u *CommandRepo) SaveRedis(command *model.RedisCommand) error {
return global.DB.Save(command).Error
}
func (u *CommandRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Command{}).Where("id = ?", id).Updates(vars).Error
}
func (h *CommandRepo) UpdateGroup(group, newGroup uint) error {
return global.DB.Model(&model.Command{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error
}
func (u *CommandRepo) Delete(opts ...DBOption) error {
db := global.DB
@ -123,14 +85,6 @@ func (u *CommandRepo) Delete(opts ...DBOption) error {
return db.Delete(&model.Command{}).Error
}
func (u *CommandRepo) DeleteRedis(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.RedisCommand{}).Error
}
func (a CommandRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {

View file

@ -1,6 +1,9 @@
package repo
import (
"fmt"
"github.com/1Panel-dev/1Panel/core/constant"
"gorm.io/gorm"
)
@ -12,6 +15,8 @@ type ICommonRepo interface {
WithByIDs(ids []uint) DBOption
WithByType(ty string) DBOption
WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
}
type CommonRepo struct{}
@ -51,3 +56,18 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
return g.Order(orderStr)
}
}
func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption {
switch order {
case constant.OrderDesc:
order = "desc"
case constant.OrderAsc:
order = "asc"
default:
orderBy = "created_at"
order = "desc"
}
return func(g *gorm.DB) *gorm.DB {
return g.Order(fmt.Sprintf("%s %s", orderBy, order))
}
}

View file

@ -1,8 +1,8 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/global"
"gorm.io/gorm"
)
@ -14,14 +14,42 @@ type IGroupRepo interface {
Create(group *model.Group) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
WithByID(id uint) DBOption
WithByGroupID(id uint) DBOption
WithByGroupType(ty string) DBOption
WithByDefault(isDefalut bool) DBOption
CancelDefault(groupType string) error
WithByHostDefault() DBOption
}
func NewIGroupRepo() IGroupRepo {
return &GroupRepo{}
}
func (c *GroupRepo) WithByID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id = ?", id)
}
}
func (c *GroupRepo) WithByGroupID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("group_id = ?", id)
}
}
func (c *GroupRepo) WithByGroupType(ty string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` = ?", ty)
}
}
func (c *GroupRepo) WithByDefault(isDefalut bool) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("is_default = ?", isDefalut)
}
}
func (u *GroupRepo) Get(opts ...DBOption) (model.Group, error) {
var group model.Group
db := global.DB
@ -50,12 +78,6 @@ func (u *GroupRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Group{}).Where("id = ?", id).Updates(vars).Error
}
func (u *GroupRepo) WithByHostDefault() DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("is_default = ? AND type = ?", 1, "host")
}
}
func (u *GroupRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
@ -65,5 +87,7 @@ func (u *GroupRepo) Delete(opts ...DBOption) error {
}
func (u *GroupRepo) CancelDefault(groupType string) error {
return global.DB.Model(&model.Group{}).Where("is_default = ? AND type = ?", 1, groupType).Updates(map[string]interface{}{"is_default": 0}).Error
return global.DB.Model(&model.Group{}).
Where("is_default = ? AND type = ?", 1, groupType).
Updates(map[string]interface{}{"is_default": 0}).Error
}

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/json"
"fmt"
"net/http"
"os"
"path"
"strings"
@ -19,6 +20,8 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
fileUtils "github.com/1Panel-dev/1Panel/core/utils/files"
httpUtils "github.com/1Panel-dev/1Panel/core/utils/http"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@ -271,12 +274,17 @@ func (u *BackupService) Delete(id uint) error {
if backup.Type == constant.Local {
return buserr.New(constant.ErrBackupLocalDelete)
}
if backup.InUsed {
return buserr.New(constant.ErrBackupInUsed)
}
if backup.Type == constant.OneDrive {
global.Cron.Remove(cron.EntryID(backup.EntryID))
}
if _, err := httpUtils.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%v", id), http.MethodGet, nil); err != nil {
global.LOG.Errorf("check used of local cronjob failed, err: %v", err)
return buserr.New(constant.ErrBackupInUsed)
}
if err := xpack.CheckBackupUsed(id); err != nil {
global.LOG.Errorf("check used of node cronjob failed, err: %v", err)
return buserr.New(constant.ErrBackupInUsed)
}
return backupRepo.Delete(commonRepo.WithByID(id))
}

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"
var (
hostRepo = repo.NewIHostRepo()
commandRepo = repo.NewICommandRepo()
commonRepo = repo.NewICommonRepo()
settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo()
logRepo = repo.NewILogRepo()
groupRepo = repo.NewIGroupRepo()
)

View file

@ -1,9 +1,17 @@
package service
import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"bytes"
"fmt"
"net/http"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
httpUtils "github.com/1Panel-dev/1Panel/core/utils/http"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
@ -11,7 +19,7 @@ import (
type GroupService struct{}
type IGroupService interface {
List(req dto.GroupSearch) ([]dto.GroupInfo, error)
List(req dto.OperateByType) ([]dto.GroupInfo, error)
Create(req dto.GroupCreate) error
Update(req dto.GroupUpdate) error
Delete(id uint) error
@ -21,8 +29,17 @@ func NewIGroupService() IGroupService {
return &GroupService{}
}
func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) {
groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type), commonRepo.WithOrderBy("is_default desc"), commonRepo.WithOrderBy("created_at desc"))
func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
options := []repo.DBOption{
commonRepo.WithByType(req.Type),
commonRepo.WithOrderBy("is_default desc"),
commonRepo.WithOrderBy("created_at desc"),
}
var (
groups []model.Group
err error
)
groups, err = groupRepo.GetList(options...)
if err != nil {
return nil, constant.ErrRecordNotFound
}
@ -56,22 +73,33 @@ func (u *GroupService) Delete(id uint) error {
if group.ID == 0 {
return constant.ErrRecordNotFound
}
if group.IsDefault {
return buserr.New(constant.ErrGroupIsDefault)
}
defaultGroup, err := groupRepo.Get(commonRepo.WithByType(group.Type), groupRepo.WithByDefault(true))
if err != nil {
return err
}
switch group.Type {
case "website":
websites, _ := websiteRepo.GetBy(websiteRepo.WithGroupID(id))
if len(websites) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
}
case "command":
commands, _ := commandRepo.GetList(commonRepo.WithByGroupID(id))
if len(commands) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
}
case "host":
hosts, _ := hostRepo.GetList(commonRepo.WithByGroupID(id))
if len(hosts) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
err = hostRepo.UpdateGroup(id, defaultGroup.ID)
case "command":
err = commandRepo.UpdateGroup(id, defaultGroup.ID)
case "node":
err = xpack.UpdateGroup("node", id, defaultGroup.ID)
case "website":
bodyItem := []byte(fmt.Sprintf(`{"Group":%v, "NewGroup":%v}`, id, defaultGroup.ID))
if _, err := httpUtils.NewLocalClient("/api/v2/websites/group/change", http.MethodPost, bytes.NewReader(bodyItem)); err != nil {
return err
}
if err := xpack.UpdateGroup("node", id, defaultGroup.ID); err != nil {
return err
}
default:
return constant.ErrNotSupportType
}
if err != nil {
return err
}
return groupRepo.Delete(commonRepo.WithByID(id))
}

View file

@ -4,11 +4,12 @@ import (
"encoding/base64"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
@ -149,8 +150,15 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
return &host, err
}
func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) {
total, hosts, err := hostRepo.Page(search.Page, search.PageSize, hostRepo.WithByInfo(search.Info), commonRepo.WithByGroupID(search.GroupID))
func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interface{}, error) {
var options []repo.DBOption
if len(req.Info) != 0 {
options = append(options, commandRepo.WithLikeName(req.Info))
}
if req.GroupID != 0 {
options = append(options, groupRepo.WithByGroupID(req.GroupID))
}
total, hosts, err := hostRepo.Page(req.Page, req.PageSize, options...)
if err != nil {
return 0, nil, err
}
@ -249,7 +257,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if req.GroupID == 0 {
group, err := groupRepo.Get(groupRepo.WithByHostDefault())
group, err := groupRepo.Get(commonRepo.WithByType("host"), groupRepo.WithByDefault(true))
if err != nil {
return nil, errors.New("get default group failed")
}
@ -299,7 +307,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
}
func (u *HostService) Delete(ids []uint) error {
hosts, _ := hostRepo.GetList(commonRepo.WithIdsIn(ids))
hosts, _ := hostRepo.GetList(commonRepo.WithByIDs(ids))
for _, host := range hosts {
if host.ID == 0 {
return constant.ErrRecordNotFound
@ -308,7 +316,7 @@ func (u *HostService) Delete(ids []uint) error {
return errors.New("the local connection information cannot be deleted!")
}
}
return hostRepo.Delete(commonRepo.WithIdsIn(ids))
return hostRepo.Delete(commonRepo.WithByIDs(ids))
}
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {

View file

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

View file

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

View file

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

View file

@ -15,13 +15,16 @@ import (
)
var AddTable = &gormigrate.Migration{
ID: "20240808-add-table",
ID: "20240819-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.OperationLog{},
&model.LoginLog{},
&model.Setting{},
&model.BackupAccount{},
&model.Group{},
&model.Host{},
&model.Command{},
)
},
}
@ -144,6 +147,35 @@ var InitSetting = &gormigrate.Migration{
},
}
var InitHost = &gormigrate.Migration{
ID: "20240816-init-host",
Migrate: func(tx *gorm.DB) error {
hostGroup := &model.Group{Name: "default", Type: "host", IsDefault: true}
if err := tx.Create(hostGroup).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "default", Type: "node", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "default", Type: "command", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "default", Type: "website", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "default", Type: "redis", IsDefault: true}).Error; err != nil {
return err
}
host := model.Host{
Name: "localhost", Addr: "127.0.0.1", User: "root", Port: 22, AuthMode: "password", GroupID: hostGroup.ID,
}
if err := tx.Create(&host).Error; err != nil {
return err
}
return nil
},
}
var InitOneDrive = &gormigrate.Migration{
ID: "20240808-init-one-drive",
Migrate: func(tx *gorm.DB) error {

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{},
&LogRouter{},
&SettingRouter{},
&CommandRouter{},
&HostRouter{},
&GroupRouter{},
}
}

View file

@ -1,14 +1,14 @@
package router
import (
v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
v2 "github.com/1Panel-dev/1Panel/core/app/api/v2"
"github.com/gin-gonic/gin"
)
type WebsiteGroupRouter struct {
type GroupRouter struct {
}
func (a *WebsiteGroupRouter) InitRouter(Router *gin.RouterGroup) {
func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("groups")
baseApi := v2.ApiGroupApp.BaseApi

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

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 {
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 interface CommandInfo {
id: number;
type: string;
name: string;
groupID: number;
command: string;
}
export interface CommandOperate {
id: number;
type: string;
name: string;
groupID: number;
command: string;
}
export interface RedisCommand {
id: number;
name: string;
command: string;
}
}

View file

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

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 http from '@/api';
export const GetGroupList = (params: Group.GroupSearch) => {
return http.post<Array<Group.GroupInfo>>(`/groups/search`, params);
export const GetGroupList = (type: string) => {
return http.post<Array<Group.GroupInfo>>(`/core/groups/search`, { type: type });
};
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) => {
return http.post(`/groups/update`, params);
return http.post(`/core/groups/update`, params);
};
export const DeleteGroup = (id: number) => {
return http.post(`/groups/del`, { id: id });
return http.post(`/core/groups/del`, { id: id });
};

View file

@ -1,90 +1,8 @@
import http from '@/api';
import { ResPage, SearchWithPage } from '../interface';
import { Command } from '../interface/command';
import { ResPage } from '../interface';
import { Host } from '../interface/host';
import { Base64 } from 'js-base64';
import { deepCopy } from '@/utils/util';
import { TimeoutEnum } from '@/enums/http-enum';
export const searchHosts = (params: Host.SearchWithPage) => {
return http.post<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
export const loadFireBaseInfo = () => {
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-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1717570629440') format('woff2'),
url('iconfont.woff?t=1717570629440') format('woff'),
url('iconfont.ttf?t=1717570629440') format('truetype'),
url('iconfont.svg?t=1717570629440#panel') format('svg');
src: url('iconfont.woff2?t=1723798525783') format('woff2'),
url('iconfont.woff?t=1723798525783') format('woff'),
url('iconfont.ttf?t=1723798525783') format('truetype'),
url('iconfont.svg?t=1723798525783#panel') format('svg');
}
.panel {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale;
}
.p-zhongduan:before {
content: "\e731";
}
.p-docker1:before {
content: "\e76a";
}
@ -178,18 +182,10 @@
content: "\e856";
}
.p-webdav:before {
content: "\e622";
}
.p-xiangqing:before {
content: "\e677";
}
.p-onedrive:before {
content: "\e601";
}
.p-caidan:before {
content: "\e61d";
}
@ -198,14 +194,6 @@
content: "\e744";
}
.p-tengxunyun1:before {
content: "\e651";
}
.p-qiniuyun:before {
content: "\e62c";
}
.p-file-png:before {
content: "\e7ae";
}
@ -250,10 +238,6 @@
content: "\e60f";
}
.p-aws:before {
content: "\e600";
}
.p-taolun:before {
content: "\e602";
}
@ -262,22 +246,10 @@
content: "\e616";
}
.p-SFTP:before {
content: "\e647";
}
.p-huaban88:before {
content: "\e67c";
}
.p-oss:before {
content: "\e607";
}
.p-minio:before {
content: "\e63c";
}
.p-logout:before {
content: "\e8fe";
}

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,13 @@
"css_prefix_text": "p-",
"description": "",
"glyphs": [
{
"icon_id": "30053592",
"name": "终端",
"font_class": "zhongduan",
"unicode": "e731",
"unicode_decimal": 59185
},
{
"icon_id": "1064806",
"name": "docker",
@ -292,13 +299,6 @@
"unicode": "e856",
"unicode_decimal": 59478
},
{
"icon_id": "23044673",
"name": "webdav",
"font_class": "webdav",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "10293150",
"name": "详情",
@ -306,13 +306,6 @@
"unicode": "e677",
"unicode_decimal": 58999
},
{
"icon_id": "13015332",
"name": "onedrive",
"font_class": "onedrive",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "7708032",
"name": "菜单",
@ -327,20 +320,6 @@
"unicode": "e744",
"unicode_decimal": 59204
},
{
"icon_id": "12959160",
"name": "腾讯云",
"font_class": "tengxunyun1",
"unicode": "e651",
"unicode_decimal": 58961
},
{
"icon_id": "24877229",
"name": "七牛云",
"font_class": "qiniuyun",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "19671162",
"name": "png-1",
@ -418,13 +397,6 @@
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "32101973",
"name": "Amazon_Web_Services_Logo",
"font_class": "aws",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "1760690",
"name": "讨论",
@ -439,13 +411,6 @@
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "13532955",
"name": "SFTP",
"font_class": "SFTP",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "15337722",
"name": "Logo GitHub",
@ -453,20 +418,6 @@
"unicode": "e67c",
"unicode_decimal": 59004
},
{
"icon_id": "16268521",
"name": "oss",
"font_class": "oss",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "20290513",
"name": "minio",
"font_class": "minio",
"unicode": "e63c",
"unicode_decimal": 58940
},
{
"icon_id": "924436",
"name": "logout",

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 res = await GetGroupList({ type: dialogData.value.groupType });
const res = await GetGroupList(dialogData.value.groupType);
groupList.value = res.data;
for (const group of groupList.value) {
if (group.name === groupName) {

View file

@ -21,7 +21,13 @@
{{ $t('commons.table.default') }}
</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>
<el-form @submit.prevent ref="groupForm" v-if="row.edit" :model="row">
@ -31,6 +37,24 @@
</el-form>
</template>
</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')">
<template #default="{ row, $index }">
<div>
@ -52,7 +76,12 @@
<el-button link v-if="row.edit" type="primary" @click="search()">
{{ $t('commons.button.cancel') }}
</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') }}
</el-button>
</div>
@ -92,17 +121,8 @@ const acceptParams = (params: DialogProps): void => {
const emit = defineEmits<{ (e: 'search'): void }>();
const search = () => {
data.value = [];
GetGroupList({ type: type.value }).then((res) => {
for (const d of res.data) {
const g = {
id: d.id,
name: d.name,
isDefault: d.isDefault,
edit: false,
};
data.value.push(g);
}
GetGroupList(type.value).then((res) => {
data.value = res.data || [];
});
};
@ -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 = () => {
for (const d of data.value) {
if (d.name == '') {
@ -150,6 +182,7 @@ const openCreate = () => {
name: '',
isDefault: false,
edit: true,
status: 'Enable',
};
data.value.unshift(g);
};

View file

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

View file

@ -40,17 +40,6 @@ const hostRouter = {
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',
name: 'FirewallPort',

View file

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

View file

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const settingRouter = {
sort: 10,
sort: 12,
path: '/settings',
component: Layout,
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">
<template #default="scope">
<el-button
v-if="scope.row.lineStatus === 'create'"
v-if="scope.row.lineStatus === 'create' || scope.row.lineStatus === 'edit'"
link
type="primary"
@click="handleCmdSave(scope.row)"
@ -38,15 +38,20 @@
v-if="!scope.row.lineStatus || scope.row.lineStatus === 'saved'"
link
type="primary"
@click="scope.row.lineStatus = 'create'"
@click="scope.row.lineStatus = 'edit'"
>
{{ $t('commons.button.edit') }}
</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') }}
</el-button>
<el-button
v-if="scope.row.lineStatus !== 'create'"
v-if="scope.row.lineStatus !== 'create' && scope.row.lineStatus !== 'edit'"
link
type="primary"
@click="handleCmdDelete(scope.$index)"
@ -68,7 +73,7 @@
<script setup lang="ts">
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 i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
@ -81,6 +86,8 @@ const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
orderBy: 'name',
order: 'ascending',
});
const opRef = ref();
@ -99,6 +106,7 @@ const handleCmdAdd = () => {
let item = {
name: '',
command: '',
type: 'redis',
lineStatus: 'create',
};
data.value.push(item);
@ -113,7 +121,8 @@ const handleCmdSave = async (row: any) => {
return;
}
loading.value = true;
await saveRedisCommand(row)
if (row.lineStatus === 'create') {
await addCommand(row)
.then(() => {
loading.value = false;
row.lineStatus = 'saved';
@ -124,9 +133,20 @@ const handleCmdSave = async (row: any) => {
loading.value = false;
});
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 ids = [];
if (row) {
@ -145,19 +165,24 @@ const batchDelete = async (row: Command.RedisCommand | null) => {
i18n.global.t('terminal.quickCommand'),
i18n.global.t('commons.button.delete'),
]),
api: deleteRedisCommand,
api: deleteCommand,
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 = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
orderBy: paginationConfig.orderBy,
order: paginationConfig.order,
info: '',
type: 'redis',
};
loading.value = true;
await getRedisCommandPage(params)
await getCommandPage(params)
.then((res) => {
loading.value = false;
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 { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
import { getRedisCommandList } from '@/api/modules/host';
import { getCommandList } from '@/api/modules/command';
const globalStore = GlobalStore();
const loading = ref(false);
@ -364,7 +364,7 @@ const installCli = async () => {
};
const loadQuickCmd = async () => {
const res = await getRedisCommandList();
const res = await getCommandList('redis');
quickCmdList.value = res.data || [];
};

View file

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

View file

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

View file

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

View file

@ -81,7 +81,7 @@
import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus';
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 i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
@ -122,7 +122,7 @@ const rules = reactive({
});
const loadGroups = async () => {
const res = await GetGroupList({ type: 'host' });
const res = await GetGroupList('host');
groupList.value = res.data;
if (dialogData.value.title === 'create') {
for (const item of groupList.value) {

View file

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

View file

@ -68,7 +68,7 @@
import { ElForm } from 'element-plus';
import { Host } from '@/api/interface/host';
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 { reactive, ref } from 'vue';
import { MsgError, MsgSuccess } from '@/utils/message';

View file

@ -143,15 +143,16 @@
<script setup lang="ts">
import { ref, getCurrentInstance, watch, nextTick, computed, onMounted } from '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 { ElTree } from 'element-plus';
import screenfull from 'screenfull';
import i18n from '@/lang';
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 router from '@/routers';
import { getCommandTree } from '@/api/modules/command';
const dialogRef = ref();
const ctx = getCurrentInstance() as any;
@ -287,7 +288,7 @@ const filterHost = (value: string, data: any) => {
return data.label.includes(value);
};
const loadCommandTree = async () => {
const res = await getCommandTree();
const res = await getCommandTree('command');
commandTree.value = res.data || [];
};
@ -348,7 +349,7 @@ const onReconnect = async (item: any) => {
nextTick(() => {
ctx.refs[`t-${item.index}`] &&
ctx.refs[`t-${item.index}`][0].acceptParams({
endpoint: '/api/v2/terminals',
endpoint: '/api/v2/core/hosts/terminal',
args: `id=${item.wsID}`,
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(() => {
ctx.refs[`t-${terminalValue.value}`] &&
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
endpoint: '/api/v2/terminals',
endpoint: '/api/v2/core/hosts/terminal',
args: `id=${wsID}`,
initCmd: initCmd.value,
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 res = await GetGroupList({ type: 'website' });
const res = await GetGroupList('website');
groups.value = res.data;
GetWebsite(websiteId.value).then((res) => {

View file

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

View file

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

2
go.mod
View file

@ -5,6 +5,7 @@ go 1.22.4
require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go v1.55.5
github.com/creack/pty v1.1.9
github.com/dgraph-io/badger/v4 v4.2.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/gzip v1.0.1
@ -15,6 +16,7 @@ require (
github.com/goh-chunlin/go-onedrive v1.1.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jinzhu/copier v0.4.0
github.com/minio/minio-go/v7 v7.0.74
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/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/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
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/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/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/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/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=