mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 12:39:01 +08:00
feat: 增加分组、快速命令等接口
This commit is contained in:
parent
c5b2eddd3f
commit
89432bc1b8
@ -37,6 +37,7 @@ sqlite:
|
||||
|
||||
log:
|
||||
level: info
|
||||
time_zone: Asia/Shanghai
|
||||
path: /opt/1Panel/log
|
||||
log_name: 1Panel
|
||||
log_suffix: .log
|
||||
|
98
backend/app/api/v1/command.go
Normal file
98
backend/app/api/v1/command.go
Normal file
@ -0,0 +1,98 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) CreateCommand(c *gin.Context) {
|
||||
var req dto.CommandCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := commandService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) SearchCommand(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := commandService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BaseApi) ListCommand(c *gin.Context) {
|
||||
list, err := commandService.Search()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteCommand(c *gin.Context) {
|
||||
var req dto.DeleteByName
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := commandService.Delete(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateCommand(c *gin.Context) {
|
||||
var req dto.CommandUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
if err := commandService.Update(id, upMap); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
@ -11,5 +11,7 @@ var ApiGroupApp = new(ApiGroup)
|
||||
var (
|
||||
userService = service.ServiceGroupApp.UserService
|
||||
hostService = service.ServiceGroupApp.HostService
|
||||
groupService = service.ServiceGroupApp.GroupService
|
||||
commandService = service.ServiceGroupApp.CommandService
|
||||
operationService = service.ServiceGroupApp.OperationService
|
||||
)
|
||||
|
79
backend/app/api/v1/group.go
Normal file
79
backend/app/api/v1/group.go
Normal file
@ -0,0 +1,79 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) CreateGroup(c *gin.Context) {
|
||||
var req dto.GroupCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := groupService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteGroup(c *gin.Context) {
|
||||
var req dto.DeleteByName
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := groupService.Delete(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateGroup(c *gin.Context) {
|
||||
var req dto.GroupUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
if err := groupService.Update(id, upMap); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) ListGroup(c *gin.Context) {
|
||||
list, err := groupService.Search()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) Create(c *gin.Context) {
|
||||
func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
var req dto.HostCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
@ -26,23 +26,20 @@ func (b *BaseApi) Create(c *gin.Context) {
|
||||
helper.SuccessWithData(c, host)
|
||||
}
|
||||
|
||||
func (b *BaseApi) PageHosts(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
var req dto.SearchForTree
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := hostService.Search(req)
|
||||
data, err := hostService.SearchForTree(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||
@ -81,6 +78,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
upMap["group"] = req.Group
|
||||
upMap["addr"] = req.Addr
|
||||
upMap["port"] = req.Port
|
||||
upMap["user"] = req.User
|
||||
|
@ -68,7 +68,7 @@ func (b *BaseApi) Register(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (b *BaseApi) PageUsers(c *gin.Context) {
|
||||
var req dto.UserPage
|
||||
var req dto.SearchWithPage
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
|
15
backend/app/dto/command.go
Normal file
15
backend/app/dto/command.go
Normal file
@ -0,0 +1,15 @@
|
||||
package dto
|
||||
|
||||
type CommandCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
}
|
||||
|
||||
type CommandUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type CommandInfo struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package dto
|
||||
|
||||
type SearchWithPage struct {
|
||||
PageInfo
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" validate:"required,number"`
|
||||
PageSize int `json:"pageSize" validate:"required,number"`
|
||||
@ -13,6 +18,10 @@ type BatchDeleteReq struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type DeleteByName struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type OperationWithNameAndType struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
|
10
backend/app/dto/group.go
Normal file
10
backend/app/dto/group.go
Normal file
@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
type GroupCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type GroupUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type HostCreate struct {
|
||||
Group string `json:"group" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Addr string `json:"addr" validate:"required,ip"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
@ -14,14 +17,14 @@ type HostCreate struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SearchWithPage struct {
|
||||
PageInfo
|
||||
type SearchForTree struct {
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type HostInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Group string `json:"group"`
|
||||
Name string `json:"name"`
|
||||
Addr string `json:"addr"`
|
||||
Port uint `json:"port"`
|
||||
@ -31,7 +34,17 @@ type HostInfo struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type HostTree struct {
|
||||
Label string `json:"label"`
|
||||
Children []TreeChild `json:"children"`
|
||||
}
|
||||
|
||||
type TreeChild struct {
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
type HostUpdate struct {
|
||||
Group string `json:"group" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Addr string `json:"addr" validate:"required,ip"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
|
@ -10,11 +10,6 @@ type UserCreate struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
|
||||
type UserPage struct {
|
||||
PageInfo
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type CaptchaResponse struct {
|
||||
CaptchaID string `json:"captchaID"`
|
||||
ImagePath string `json:"imagePath"`
|
||||
|
9
backend/app/model/command.go
Normal file
9
backend/app/model/command.go
Normal file
@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Command struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(64));unique;not null" json:"name"`
|
||||
Command string `gorm:"type:varchar(256);unique;not null" json:"command"`
|
||||
}
|
9
backend/app/model/group.go
Normal file
9
backend/app/model/group.go
Normal file
@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Group struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
Type string `gorm:"type:varchar(16);unique;not null" json:"type"`
|
||||
}
|
@ -4,6 +4,7 @@ import "gorm.io/gorm"
|
||||
|
||||
type Host struct {
|
||||
gorm.Model
|
||||
Group string `gorm:"type:varchar(64);not null" json:"group"`
|
||||
Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
|
||||
Addr string `gorm:"type:varchar(16);unique;not null" json:"addr"`
|
||||
Port int `gorm:"type:varchar(5);not null" json:"port"`
|
||||
|
80
backend/app/repo/command.go
Normal file
80
backend/app/repo/command.go
Normal file
@ -0,0 +1,80 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommandRepo struct{}
|
||||
|
||||
type ICommandRepo interface {
|
||||
GetList(opts ...DBOption) ([]model.Command, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.Command, error)
|
||||
WithByInfo(info string) DBOption
|
||||
Create(command *model.Command) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewICommandService() ICommandRepo {
|
||||
return &CommandRepo{}
|
||||
}
|
||||
|
||||
func (u *CommandRepo) Get(opts ...DBOption) (model.Command, error) {
|
||||
var command model.Command
|
||||
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{})
|
||||
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{})
|
||||
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 {
|
||||
return g
|
||||
}
|
||||
infoStr := "%" + info + "%"
|
||||
return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CommandRepo) Create(command *model.Command) error {
|
||||
return global.DB.Create(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 (u *CommandRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Command{}).Error
|
||||
}
|
@ -3,6 +3,8 @@ package repo
|
||||
type RepoGroup struct {
|
||||
UserRepo
|
||||
HostRepo
|
||||
GroupRepo
|
||||
CommandRepo
|
||||
OperationRepo
|
||||
CommonRepo
|
||||
}
|
||||
|
56
backend/app/repo/group.go
Normal file
56
backend/app/repo/group.go
Normal file
@ -0,0 +1,56 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
)
|
||||
|
||||
type GroupRepo struct{}
|
||||
|
||||
type IGroupRepo interface {
|
||||
Get(opts ...DBOption) (model.Group, error)
|
||||
GetList(opts ...DBOption) ([]model.Group, error)
|
||||
Create(group *model.Group) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIGroupService() IGroupRepo {
|
||||
return &GroupRepo{}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) Get(opts ...DBOption) (model.Group, error) {
|
||||
var group model.Group
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&group).Error
|
||||
return group, err
|
||||
}
|
||||
|
||||
func (u *GroupRepo) GetList(opts ...DBOption) ([]model.Group, error) {
|
||||
var groups []model.Group
|
||||
db := global.DB.Model(&model.Group{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&groups).Error
|
||||
return groups, err
|
||||
}
|
||||
|
||||
func (u *GroupRepo) Create(group *model.Group) error {
|
||||
return global.DB.Create(group).Error
|
||||
}
|
||||
|
||||
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) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Group{}).Error
|
||||
}
|
@ -10,7 +10,7 @@ type HostRepo struct{}
|
||||
|
||||
type IHostRepo interface {
|
||||
Get(opts ...DBOption) (model.Host, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error)
|
||||
GetList(opts ...DBOption) ([]model.Host, error)
|
||||
WithByInfo(info string) DBOption
|
||||
Create(host *model.Host) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
@ -31,20 +31,21 @@ func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) {
|
||||
return host, err
|
||||
}
|
||||
|
||||
func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) {
|
||||
func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
|
||||
var hosts []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(&hosts).Error
|
||||
return count, hosts, err
|
||||
err := db.Find(&hosts).Error
|
||||
return hosts, err
|
||||
}
|
||||
|
||||
func (c *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)
|
||||
}
|
||||
|
70
backend/app/service/command.go
Normal file
70
backend/app/service/command.go
Normal file
@ -0,0 +1,70 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CommandService struct{}
|
||||
|
||||
type ICommandService interface {
|
||||
Search() ([]model.Command, error)
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
Create(commandDto dto.CommandCreate) error
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
Delete(name string) error
|
||||
}
|
||||
|
||||
func NewICommandService() ICommandService {
|
||||
return &CommandService{}
|
||||
}
|
||||
|
||||
func (u *CommandService) Search() ([]model.Command, error) {
|
||||
commands, err := commandRepo.GetList()
|
||||
if err != nil {
|
||||
return nil, constant.ErrRecordNotFound
|
||||
}
|
||||
return commands, err
|
||||
}
|
||||
|
||||
func (u *CommandService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.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())
|
||||
}
|
||||
dtoCommands = append(dtoCommands, item)
|
||||
}
|
||||
return total, dtoCommands, err
|
||||
}
|
||||
|
||||
func (u *CommandService) Create(commandDto dto.CommandCreate) 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(name string) error {
|
||||
command, _ := commandRepo.Get(commonRepo.WithByName(name))
|
||||
if command.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
return commandRepo.Delete(commonRepo.WithByID(command.ID))
|
||||
}
|
||||
|
||||
func (u *CommandService) Update(id uint, upMap map[string]interface{}) error {
|
||||
return commandRepo.Update(id, upMap)
|
||||
}
|
@ -5,6 +5,8 @@ import "github.com/1Panel-dev/1Panel/app/repo"
|
||||
type ServiceGroup struct {
|
||||
UserService
|
||||
HostService
|
||||
GroupService
|
||||
CommandService
|
||||
OperationService
|
||||
}
|
||||
|
||||
@ -13,6 +15,8 @@ var ServiceGroupApp = new(ServiceGroup)
|
||||
var (
|
||||
userRepo = repo.RepoGroupApp.UserRepo
|
||||
hostRepo = repo.RepoGroupApp.HostRepo
|
||||
groupRepo = repo.RepoGroupApp.GroupRepo
|
||||
commandRepo = repo.RepoGroupApp.CommandRepo
|
||||
operationRepo = repo.RepoGroupApp.OperationRepo
|
||||
commonRepo = repo.RepoGroupApp.CommonRepo
|
||||
)
|
||||
|
56
backend/app/service/group.go
Normal file
56
backend/app/service/group.go
Normal file
@ -0,0 +1,56 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type GroupService struct{}
|
||||
|
||||
type IGroupService interface {
|
||||
Search() ([]model.Group, error)
|
||||
Create(groupDto dto.GroupCreate) error
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
Delete(name string) error
|
||||
}
|
||||
|
||||
func NewIGroupService() IGroupService {
|
||||
return &GroupService{}
|
||||
}
|
||||
|
||||
func (u *GroupService) Search() ([]model.Group, error) {
|
||||
groups, err := groupRepo.GetList()
|
||||
if err != nil {
|
||||
return nil, constant.ErrRecordNotFound
|
||||
}
|
||||
return groups, err
|
||||
}
|
||||
|
||||
func (u *GroupService) Create(groupDto dto.GroupCreate) error {
|
||||
group, _ := groupRepo.Get(commonRepo.WithByName(groupDto.Name), commonRepo.WithByName(groupDto.Name))
|
||||
if group.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
if err := copier.Copy(&group, &groupDto); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
if err := groupRepo.Create(&group); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *GroupService) Delete(name string) error {
|
||||
group, _ := groupRepo.Get(commonRepo.WithByName(name))
|
||||
if group.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
return groupRepo.Delete(commonRepo.WithByID(group.ID))
|
||||
}
|
||||
|
||||
func (u *GroupService) Update(id uint, upMap map[string]interface{}) error {
|
||||
return groupRepo.Update(id, upMap)
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
@ -12,7 +14,7 @@ type HostService struct{}
|
||||
|
||||
type IHostService interface {
|
||||
GetConnInfo(id uint) (*model.Host, error)
|
||||
Search(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
||||
Create(hostDto dto.HostCreate) (*dto.HostInfo, error)
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
BatchDelete(ids []uint) error
|
||||
@ -30,17 +32,25 @@ func (u *HostService) GetConnInfo(id uint) (*model.Host, error) {
|
||||
return &host, err
|
||||
}
|
||||
|
||||
func (u *HostService) Search(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, hosts, err := hostRepo.Page(search.Page, search.PageSize, hostRepo.WithByInfo(search.Info))
|
||||
var dtoHosts []dto.HostInfo
|
||||
func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) {
|
||||
hosts, err := hostRepo.GetList(hostRepo.WithByInfo(search.Info))
|
||||
distinctMap := make(map[string][]string)
|
||||
for _, host := range hosts {
|
||||
var item dto.HostInfo
|
||||
if err := copier.Copy(&item, &host); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
if _, ok := distinctMap[host.Group]; !ok {
|
||||
distinctMap[host.Group] = []string{fmt.Sprintf("%s@%s:%d", host.User, host.Addr, host.Port)}
|
||||
} else {
|
||||
distinctMap[host.Group] = append(distinctMap[host.Group], fmt.Sprintf("%s@%s:%d", host.User, host.Addr, host.Port))
|
||||
}
|
||||
dtoHosts = append(dtoHosts, item)
|
||||
}
|
||||
return total, dtoHosts, err
|
||||
var data []dto.HostTree
|
||||
for key, value := range distinctMap {
|
||||
var children []dto.TreeChild
|
||||
for _, label := range value {
|
||||
children = append(children, dto.TreeChild{Label: label})
|
||||
}
|
||||
data = append(data, dto.HostTree{Label: key, Children: children})
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *HostService) Create(hostDto dto.HostCreate) (*dto.HostInfo, error) {
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/init/session/psession"
|
||||
"github.com/1Panel-dev/1Panel/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/utils/jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -18,7 +17,7 @@ type UserService struct{}
|
||||
|
||||
type IUserService interface {
|
||||
Get(id uint) (*dto.UserInfo, error)
|
||||
Page(search dto.UserPage) (int64, interface{}, error)
|
||||
Page(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
Register(userDto dto.UserCreate) error
|
||||
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
|
||||
LogOut(c *gin.Context) error
|
||||
@ -44,7 +43,7 @@ func (u *UserService) Get(id uint) (*dto.UserInfo, error) {
|
||||
return &dtoUser, err
|
||||
}
|
||||
|
||||
func (u *UserService) Page(search dto.UserPage) (int64, interface{}, error) {
|
||||
func (u *UserService) Page(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, users, err := userRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Name))
|
||||
var dtoUsers []dto.UserInfo
|
||||
for _, user := range users {
|
||||
@ -92,13 +91,9 @@ func (u *UserService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
|
||||
}
|
||||
return &dto.UserLoginInfo{Name: user.Name, Token: token}, err
|
||||
}
|
||||
sessionUser := psession.SessionUser{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
}
|
||||
lifeTime := global.CONF.Session.ExpiresTime
|
||||
sID, _ := c.Cookie(global.CONF.Session.SessionName)
|
||||
sessionUser, err = global.SESSION.Get(sID)
|
||||
sessionUser, err := global.SESSION.Get(sID)
|
||||
if err != nil {
|
||||
sID = uuid.NewV4().String()
|
||||
c.SetCookie(global.CONF.Session.SessionName, sID, lifeTime, "", "", false, false)
|
||||
|
Binary file not shown.
@ -2,6 +2,7 @@ package configs
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
TimeZone string `mapstructure:"timeZone"`
|
||||
Path string `mapstructure:"path"`
|
||||
LogName string `mapstructure:"log_name"`
|
||||
LogSuffix string `mapstructure:"log_suffix"`
|
||||
|
@ -1,7 +1,10 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/configs"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
@ -20,10 +23,10 @@ func setOutput(log *logrus.Logger, config configs.LogConfig) {
|
||||
filePath := path.Join(config.Path, config.LogName+config.LogSuffix)
|
||||
logPrint := &lumberjack.Logger{
|
||||
Filename: filePath,
|
||||
MaxSize: config.LogSize, // 日志文件大小,单位是 MB
|
||||
MaxBackups: config.LogBackup, // 最大过期日志保留个数
|
||||
MaxAge: config.LogData, // 保留过期文件最大时间,单位 天
|
||||
Compress: true, // 是否压缩日志,默认是不压缩。这里设置为true,压缩日志
|
||||
MaxSize: config.LogSize,
|
||||
MaxBackups: config.LogBackup,
|
||||
MaxAge: config.LogData,
|
||||
Compress: true,
|
||||
}
|
||||
level, err := logrus.ParseLevel(config.Level)
|
||||
if err != nil {
|
||||
@ -31,4 +34,24 @@ func setOutput(log *logrus.Logger, config configs.LogConfig) {
|
||||
}
|
||||
log.SetOutput(logPrint)
|
||||
log.SetLevel(level)
|
||||
log.SetFormatter(new(MineFormatter))
|
||||
}
|
||||
|
||||
type MineFormatter struct{}
|
||||
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
var cstSh, _ = time.LoadLocation(global.CONF.LogConfig.TimeZone)
|
||||
detailInfo := ""
|
||||
if entry.Caller != nil {
|
||||
funcion := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/", "")
|
||||
detailInfo = fmt.Sprintf("(%s: %d)", funcion, entry.Caller.Line)
|
||||
}
|
||||
if len(entry.Data) == 0 {
|
||||
msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().In(cstSh).Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo)
|
||||
return []byte(msg), nil
|
||||
}
|
||||
msg := fmt.Sprintf("[%s] [%s] %s %s {%v} \n", time.Now().In(cstSh).Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo, entry.Data)
|
||||
return []byte(msg), nil
|
||||
}
|
||||
|
@ -18,5 +18,5 @@ func Init() {
|
||||
global.LOG.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
global.LOG.Infof("Migration did run successfully")
|
||||
global.LOG.Info("Migration did run successfully")
|
||||
}
|
||||
|
24
backend/router/command.go
Normal file
24
backend/router/command.go
Normal file
@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CommandRouter struct{}
|
||||
|
||||
func (s *CommandRouter) InitCommandRouter(Router *gin.RouterGroup) {
|
||||
userRouter := Router.Group("commands")
|
||||
userRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
||||
withRecordRouter := userRouter.Use(middleware.OperationRecord())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
withRecordRouter.POST("", baseApi.CreateCommand)
|
||||
withRecordRouter.POST("/del", baseApi.DeleteCommand)
|
||||
userRouter.POST("/search", baseApi.SearchCommand)
|
||||
userRouter.GET("", baseApi.ListCommand)
|
||||
userRouter.PUT(":id", baseApi.UpdateCommand)
|
||||
}
|
||||
}
|
23
backend/router/group.go
Normal file
23
backend/router/group.go
Normal file
@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type GroupRouter struct{}
|
||||
|
||||
func (s *GroupRouter) InitGroupRouter(Router *gin.RouterGroup) {
|
||||
userRouter := Router.Group("group")
|
||||
userRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
||||
withRecordRouter := userRouter.Use(middleware.OperationRecord())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
withRecordRouter.POST("", baseApi.CreateGroup)
|
||||
withRecordRouter.POST("/del", baseApi.DeleteGroup)
|
||||
userRouter.GET("", baseApi.ListGroup)
|
||||
userRouter.PUT(":id", baseApi.UpdateGroup)
|
||||
}
|
||||
}
|
@ -15,9 +15,9 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
withRecordRouter := userRouter.Use(middleware.OperationRecord())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
withRecordRouter.POST("", baseApi.Create)
|
||||
withRecordRouter.POST("", baseApi.CreateHost)
|
||||
withRecordRouter.POST("/del", baseApi.DeleteHost)
|
||||
userRouter.POST("/search", baseApi.PageHosts)
|
||||
userRouter.POST("/search", baseApi.HostTree)
|
||||
userRouter.PUT(":id", baseApi.UpdateHost)
|
||||
}
|
||||
}
|
||||
|
@ -80,9 +80,7 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
return
|
||||
}
|
||||
msgObj := wsMsg{}
|
||||
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
||||
global.LOG.Errorf("unmarshal websocket message %s failed, err: %v", wsData, err)
|
||||
}
|
||||
_ = json.Unmarshal(wsData, &msgObj)
|
||||
switch msgObj.Type {
|
||||
case wsMsgResize:
|
||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||
|
@ -54,7 +54,7 @@ type LogicSshWsSession struct {
|
||||
session *ssh.Session
|
||||
wsConn *websocket.Conn
|
||||
isAdmin bool
|
||||
IsFlagged bool `comment:"当前session是否包含禁止命令"`
|
||||
IsFlagged bool
|
||||
}
|
||||
|
||||
func NewLogicSshWsSession(cols, rows int, isAdmin bool, sshClient *ssh.Client, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
|
||||
@ -127,9 +127,7 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
return
|
||||
}
|
||||
msgObj := wsMsg{}
|
||||
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
||||
global.LOG.Errorf("unmarshal websocket message %s failed, err: %v", wsData, err)
|
||||
}
|
||||
_ = json.Unmarshal(wsData, &msgObj)
|
||||
switch msgObj.Type {
|
||||
case wsMsgResize:
|
||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { CommonModel, ReqPage } from '.';
|
||||
import { CommonModel } from '.';
|
||||
|
||||
export namespace Host {
|
||||
export interface HostTree {
|
||||
label: string;
|
||||
children: Array<string>;
|
||||
}
|
||||
export interface Host extends CommonModel {
|
||||
name: string;
|
||||
addr: string;
|
||||
@ -21,7 +25,7 @@ export namespace Host {
|
||||
|
||||
description: string;
|
||||
}
|
||||
export interface ReqSearchWithPage extends ReqPage {
|
||||
export interface ReqSearch {
|
||||
info?: string;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import http from '@/api';
|
||||
import { ResPage } from '../interface';
|
||||
import { Host } from '../interface/host';
|
||||
|
||||
export const getHostList = (params: Host.ReqSearchWithPage) => {
|
||||
return http.post<ResPage<Host.Host>>(`/hosts/search`, params);
|
||||
export const getHostList = (params: Host.ReqSearch) => {
|
||||
return http.post<Array<Host.HostTree>>(`/hosts/search`, params);
|
||||
};
|
||||
|
||||
export const addHost = (params: Host.HostOperate) => {
|
||||
|
@ -5,7 +5,3 @@ import { ResOperationLog } from '../interface/operation-log';
|
||||
export const getOperationList = (info: ReqPage) => {
|
||||
return http.post<ResPage<ResOperationLog>>(`/operations`, info);
|
||||
};
|
||||
|
||||
export const deleteOperation = (params: { ids: number[] }) => {
|
||||
return http.post(`/operations/del`, params);
|
||||
};
|
||||
|
@ -91,8 +91,9 @@ export default {
|
||||
logout: '退出登录',
|
||||
},
|
||||
terminal: {
|
||||
connHistory: '历史连接',
|
||||
hostHistory: '历史主机信息',
|
||||
conn: '连接',
|
||||
hostList: '主机信息',
|
||||
quickCmd: '快捷命令',
|
||||
addHost: '添加主机',
|
||||
localhost: '本地服务器',
|
||||
name: '名称',
|
||||
|
@ -15,9 +15,6 @@ const demoRouter = {
|
||||
path: '/demos/table',
|
||||
name: 'Table',
|
||||
component: () => import('@/views/demos/table/index.vue'),
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/demos/table/:op/:id?',
|
||||
@ -27,7 +24,6 @@ const demoRouter = {
|
||||
component: () => import('@/views/demos/table/operate/index.vue'),
|
||||
meta: {
|
||||
activeMenu: '/demos/table',
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -15,7 +15,6 @@ const operationRouter = {
|
||||
name: 'OperationLog',
|
||||
component: () => import('@/views/operation-log/index.vue'),
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
requiresAuth: true,
|
||||
key: 'OperationLog',
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const terminalRouter = {
|
||||
sort: 2,
|
||||
@ -6,18 +7,44 @@ const terminalRouter = {
|
||||
component: Layout,
|
||||
redirect: '/terminal',
|
||||
meta: {
|
||||
title: 'menu.terminal',
|
||||
title: i18n.global.t('menu.terminal'),
|
||||
icon: 'monitor',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/terminal',
|
||||
path: '/terminals/terminal',
|
||||
name: 'Terminal',
|
||||
component: () => import('@/views/terminal/index.vue'),
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
requiresAuth: true,
|
||||
key: 'Terminal',
|
||||
title: i18n.global.t('terminal.conn'),
|
||||
icon: 'connection',
|
||||
activeMenu: '/terminals',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/terminals/host',
|
||||
name: 'Host',
|
||||
component: () => import('@/views/terminal/host/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
key: 'Host',
|
||||
title: i18n.global.t('terminal.hostList'),
|
||||
icon: 'platform',
|
||||
activeMenu: '/terminals',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/terminals/command',
|
||||
name: 'Command',
|
||||
component: () => import('@/views/terminal/command/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
key: 'Command',
|
||||
title: i18n.global.t('terminal.quickCmd'),
|
||||
icon: 'reading',
|
||||
activeMenu: '/terminals',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -178,3 +178,15 @@
|
||||
}
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.row-box {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
}
|
||||
.row-box .el-card {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
margin-right: 20px;
|
||||
border: 0;
|
||||
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
}
|
@ -1,80 +1,74 @@
|
||||
<template>
|
||||
<LayoutContent :header="$t('menu.operations')">
|
||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('operations.operatoin')" fix>
|
||||
<template #default="{ row }">
|
||||
{{ fmtOperation(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('operations.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == '200'" class="ml-2" type="success">{{ row.status }}</el-tag>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
:title="$t('commons.table.message')"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.errorMessage"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag class="ml-2" type="warning">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="IP" prop="ip" />
|
||||
<el-table-column align="left" :label="$t('operations.request')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" :label="$t('operations.response')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||
</ComplexTable>
|
||||
</LayoutContent>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column :label="$t('operations.operatoin')" fix>
|
||||
<template #default="{ row }">
|
||||
{{ fmtOperation(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('operations.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == '200'" class="ml-2" type="success">{{ row.status }}</el-tag>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
:title="$t('commons.table.message')"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.errorMessage"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag class="ml-2" type="warning">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="IP" prop="ip" />
|
||||
<el-table-column align="left" :label="$t('operations.request')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" :label="$t('operations.response')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { getOperationList, deleteOperation } from '@/api/modules/operation-log';
|
||||
import { getOperationList } from '@/api/modules/operation-log';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import { ResOperationLog } from '@/api/interface/operation-log';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const data = ref();
|
||||
@ -89,29 +83,6 @@ const logSearch = reactive({
|
||||
pageSize: 5,
|
||||
});
|
||||
|
||||
const selects = ref<any>([]);
|
||||
const batchDelete = async (row: ResOperationLog | null) => {
|
||||
let ids: Array<number> = [];
|
||||
|
||||
if (row === null) {
|
||||
selects.value.forEach((item: ResOperationLog) => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
} else {
|
||||
ids.push(row.id);
|
||||
}
|
||||
await useDeleteData(deleteOperation, { ids: ids }, 'commons.msg.delete');
|
||||
search();
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
type: 'danger',
|
||||
click: batchDelete,
|
||||
},
|
||||
];
|
||||
|
||||
const search = async () => {
|
||||
logSearch.page = paginationConfig.currentPage;
|
||||
logSearch.pageSize = paginationConfig.pageSize;
|
||||
|
144
frontend/src/views/terminal/host/index.vue
Normal file
144
frontend/src/views/terminal/host/index.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<el-row style="margin: 20px; margin-left: 20px" class="row-box" :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card class="el-card">
|
||||
<el-button icon="Plus" @click="readyForCreate" size="small" />
|
||||
<el-button icon="FolderAdd" @click="(folderCreate = true), (newGroupName = '')" size="small" />
|
||||
<el-button icon="Expand" @click="setTreeStatus(true)" size="small" />
|
||||
<el-button icon="Fold" @click="setTreeStatus(false)" size="small" />
|
||||
<el-input size="small" @input="loadHost" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
||||
<template #append><el-button icon="search" @click="loadHost" /></template>
|
||||
</el-input>
|
||||
<el-input size="small" v-if="folderCreate" clearable style="margin-top: 5px" v-model="newGroupName">
|
||||
<template #append>
|
||||
<el-button-group>
|
||||
<el-button icon="Check" @click="loadHost" />
|
||||
<el-button icon="Close" @click="folderCreate = false" />
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-tree ref="tree" :default-expand-all="true" :data="hostTree" :props="defaultProps" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-card class="el-card">
|
||||
<el-form ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model="hostInfo.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP" prop="addr">
|
||||
<el-input clearable v-model="hostInfo.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.port')" prop="port">
|
||||
<el-input clearable v-model.number="hostInfo.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.user')" prop="user">
|
||||
<el-input clearable v-model="hostInfo.user" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
||||
<el-radio-group v-model="hostInfo.authMode">
|
||||
<el-radio label="password">{{ $t('terminal.passwordMode') }}</el-radio>
|
||||
<el-radio label="key">{{ $t('terminal.keyMode') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('terminal.password')"
|
||||
v-if="hostInfo.authMode === 'password'"
|
||||
prop="password"
|
||||
>
|
||||
<el-input clearable show-password type="password" v-model="hostInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.key')" v-if="hostInfo.authMode === 'key'" prop="privateKey">
|
||||
<el-input clearable type="textarea" v-model="hostInfo.privateKey" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitAddHost(hostInfoRef)">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { getHostList, addHost } from '@/api/modules/host';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const hostInfoRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
addr: [Rules.requiredInput, Rules.ip],
|
||||
port: [Rules.requiredInput, Rules.port],
|
||||
user: [Rules.requiredInput],
|
||||
authMode: [Rules.requiredSelect],
|
||||
password: [Rules.requiredInput],
|
||||
privateKey: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
let hostInfo = reactive<Host.HostOperate>({
|
||||
id: 0,
|
||||
name: '',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: '',
|
||||
authMode: 'password',
|
||||
password: '',
|
||||
privateKey: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
let searcConfig = reactive<Host.ReqSearch>({
|
||||
info: '',
|
||||
});
|
||||
const tree = ref<any>(null);
|
||||
const hostTree = ref<Array<Host.HostTree>>();
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
};
|
||||
const newGroupName = ref();
|
||||
const folderCreate = ref<boolean>(false);
|
||||
|
||||
const loadHost = async () => {
|
||||
const res = await getHostList(searcConfig);
|
||||
hostTree.value = res.data;
|
||||
};
|
||||
|
||||
function setTreeStatus(expend: boolean) {
|
||||
for (let i = 0; i < tree.value.store._getAllNodes().length; i++) {
|
||||
tree.value.store._getAllNodes()[i].expanded = expend;
|
||||
}
|
||||
}
|
||||
|
||||
function readyForCreate() {
|
||||
if (hostInfoRef.value) {
|
||||
hostInfoRef.value.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
const submitAddHost = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
await addHost(hostInfo);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
// loadHost();
|
||||
} catch (error) {
|
||||
ElMessage.success(i18n.global.t('commons.msg.loginSuccess') + ':' + error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadHost();
|
||||
});
|
||||
</script>
|
@ -1,173 +1,189 @@
|
||||
<template>
|
||||
<LayoutContent :header="$t('menu.terminal')">
|
||||
<el-button class="drawer-container" icon="arrowLeftBold" @click="hostDrawer = true">
|
||||
{{ $t('terminal.connHistory') }}
|
||||
</el-button>
|
||||
<!-- <el-button class="drawer-container" icon="arrowLeftBold" @click="hostDrawer = true">
|
||||
{{ $t('terminal.connHistory') }}
|
||||
</el-button> -->
|
||||
|
||||
<div>
|
||||
<el-tabs
|
||||
type="card"
|
||||
editable
|
||||
style="background-color: #efefef"
|
||||
v-model="terminalValue"
|
||||
@edit="handleTabsEdit"
|
||||
>
|
||||
<el-tab-pane :key="item.key" v-for="item in terminalTabs" :label="item.title" :name="item.key">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon color="#67C23A" v-if="item.status === 'online'"><circleCheck /></el-icon>
|
||||
<el-icon color="#F56C6C" v-if="item.status === 'closed'"><circleClose /></el-icon>
|
||||
<span> {{ item.title }} </span>
|
||||
</span>
|
||||
</template>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 265px); background-color: #000"
|
||||
:ref="'Ref' + item.key"
|
||||
:wsID="item.wsID"
|
||||
:terminalID="item.key"
|
||||
></Terminal>
|
||||
</el-tab-pane>
|
||||
<div v-if="terminalTabs.length === 0">
|
||||
<el-empty
|
||||
style="background-color: #000; height: calc(100vh - 265px)"
|
||||
:description="$t('terminal.emptyTerminal')"
|
||||
></el-empty>
|
||||
</div>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<el-drawer :size="320" v-model="hostDrawer" :title="$t('terminal.hostHistory')" direction="rtl">
|
||||
<el-input size="small" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
||||
<template #prepend>
|
||||
<el-button icon="plus" @click="onAddHost">{{ $t('commons.button.add') }}</el-button>
|
||||
<div>
|
||||
<el-tabs
|
||||
type="card"
|
||||
editable
|
||||
class="terminal-tabs"
|
||||
style="background-color: #efefef"
|
||||
v-model="terminalValue"
|
||||
@edit="handleTabsEdit"
|
||||
>
|
||||
<el-tab-pane :key="item.key" v-for="item in terminalTabs" :label="item.title" :name="item.key">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon color="#67C23A" v-if="item.status === 'online'"><circleCheck /></el-icon>
|
||||
<el-icon color="#F56C6C" v-if="item.status === 'closed'"><circleClose /></el-icon>
|
||||
<span> {{ item.title }} </span>
|
||||
</span>
|
||||
</template>
|
||||
<template #append><el-button icon="search" @click="loadHost" /></template>
|
||||
</el-input>
|
||||
<div v-infinite-scroll="nextPage" style="overflow: auto">
|
||||
<el-card
|
||||
@click="onConnLocal()"
|
||||
style="margin-top: 5px; cursor: pointer"
|
||||
:title="$t('terminal.localhost')"
|
||||
shadow="hover"
|
||||
>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 210px); background-color: #000"
|
||||
:ref="'Ref' + item.key"
|
||||
:wsID="item.wsID"
|
||||
:terminalID="item.key"
|
||||
></Terminal>
|
||||
<div style="background-color: #000">
|
||||
<el-select v-model="quickCmd" style="width: 25%" class="m-2" placeholder="Select" size="small">
|
||||
<el-option v-for="cmd in quickCmds" :key="cmd.value" :label="cmd.label" :value="cmd.value" />
|
||||
</el-select>
|
||||
<el-input v-model="batchInput" class="terminal-input" size="small">
|
||||
<template #append>
|
||||
<el-switch size="small" v-model="isBatch" class="ml-2" />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<div v-if="terminalTabs.length === 0">
|
||||
<el-empty
|
||||
style="background-color: #000; height: calc(100vh - 210px)"
|
||||
:description="$t('terminal.emptyTerminal')"
|
||||
></el-empty>
|
||||
</div>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- <el-drawer :size="320" v-model="hostDrawer" :title="$t('terminal.hostHistory')" direction="rtl">
|
||||
<el-input size="small" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
||||
<template #prepend>
|
||||
<el-button icon="plus" @click="onAddHost">{{ $t('commons.button.add') }}</el-button>
|
||||
</template>
|
||||
<template #append><el-button icon="search" @click="loadHost" /></template>
|
||||
</el-input>
|
||||
<div v-infinite-scroll="nextPage" style="overflow: auto">
|
||||
<el-card
|
||||
@click="onConnLocal()"
|
||||
style="margin-top: 5px; cursor: pointer"
|
||||
:title="$t('terminal.localhost')"
|
||||
shadow="hover"
|
||||
>
|
||||
<div :inline="true">
|
||||
<div>
|
||||
<span>{{ $t('terminal.localhost') }}</span>
|
||||
</div>
|
||||
<span style="font-size: 14px; line-height: 25px"> [ 127.0.0.1 ]</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<div v-for="(item, index) in data" :key="item.id" @mouseover="hover = index" @mouseleave="hover = null">
|
||||
<el-card @click="onConn(item)" style="margin-top: 5px; cursor: pointer" shadow="hover">
|
||||
<div :inline="true">
|
||||
<div>
|
||||
<span>{{ $t('terminal.localhost') }}</span>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
<span style="font-size: 14px; line-height: 25px"> [ 127.0.0.1 ]</span>
|
||||
<span style="font-size: 14px; line-height: 25px">
|
||||
[ {{ item.addr + ':' + item.port }} ]
|
||||
<el-button
|
||||
style="float: right; margin-left: 5px"
|
||||
size="small"
|
||||
circle
|
||||
@click="onDeleteHost(item)"
|
||||
v-if="hover === index"
|
||||
icon="delete"
|
||||
></el-button>
|
||||
<el-button
|
||||
style="float: right; margin-left: 5px"
|
||||
size="small"
|
||||
circle
|
||||
@click="onEditHost(item)"
|
||||
v-if="hover === index"
|
||||
icon="edit"
|
||||
></el-button>
|
||||
<div v-if="item.description && hover === index">
|
||||
<span style="font-size: 12px">{{ item.description }}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<div v-for="(item, index) in data" :key="item.id" @mouseover="hover = index" @mouseleave="hover = null">
|
||||
<el-card @click="onConn(item)" style="margin-top: 5px; cursor: pointer" shadow="hover">
|
||||
<div :inline="true">
|
||||
<div>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
<span style="font-size: 14px; line-height: 25px">
|
||||
[ {{ item.addr + ':' + item.port }} ]
|
||||
<el-button
|
||||
style="float: right; margin-left: 5px"
|
||||
size="small"
|
||||
circle
|
||||
@click="onDeleteHost(item)"
|
||||
v-if="hover === index"
|
||||
icon="delete"
|
||||
></el-button>
|
||||
<el-button
|
||||
style="float: right; margin-left: 5px"
|
||||
size="small"
|
||||
circle
|
||||
@click="onEditHost(item)"
|
||||
v-if="hover === index"
|
||||
icon="edit"
|
||||
></el-button>
|
||||
<div v-if="item.description && hover === index">
|
||||
<span style="font-size: 12px">{{ item.description }}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</el-drawer> -->
|
||||
|
||||
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%">
|
||||
<el-form ref="hostInfoRef" label-width="80px" :model="hostInfo" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model="hostInfo.name" style="width: 80%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP" prop="addr">
|
||||
<el-input clearable v-model="hostInfo.addr" style="width: 80%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.port')" prop="port">
|
||||
<el-input clearable v-model.number="hostInfo.port" style="width: 80%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.user')" prop="user">
|
||||
<el-input clearable v-model="hostInfo.user" style="width: 80%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
||||
<el-radio-group v-model="hostInfo.authMode">
|
||||
<el-radio label="password">{{ $t('terminal.passwordMode') }}</el-radio>
|
||||
<el-radio label="key">{{ $t('terminal.keyMode') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.password')" v-if="hostInfo.authMode === 'password'" prop="password">
|
||||
<el-input clearable show-password type="password" v-model="hostInfo.password" style="width: 80%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.key')" v-if="hostInfo.authMode === 'key'" prop="privateKey">
|
||||
<el-input clearable type="textarea" v-model="hostInfo.privateKey" style="width: 80%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="connVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-if="operation === 'conn'" type="primary" @click="submitAddHost(hostInfoRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button v-else type="primary" @click="submitAddHost(hostInfoRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</LayoutContent>
|
||||
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%">
|
||||
<el-form ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model="hostInfo.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP" prop="addr">
|
||||
<el-input clearable v-model="hostInfo.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.port')" prop="port">
|
||||
<el-input clearable v-model.number="hostInfo.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.user')" prop="user">
|
||||
<el-input clearable v-model="hostInfo.user" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
||||
<el-radio-group v-model="hostInfo.authMode">
|
||||
<el-radio label="password">{{ $t('terminal.passwordMode') }}</el-radio>
|
||||
<el-radio label="key">{{ $t('terminal.keyMode') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.password')" v-if="hostInfo.authMode === 'password'" prop="password">
|
||||
<el-input clearable show-password type="password" v-model="hostInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('terminal.key')" v-if="hostInfo.authMode === 'key'" prop="privateKey">
|
||||
<el-input clearable type="textarea" v-model="hostInfo.privateKey" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="connVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<!-- <el-button v-if="operation === 'conn'" type="primary" @click="submitAddHost(hostInfoRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button v-else type="primary" @click="submitAddHost(hostInfoRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button> -->
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onBeforeMount, ref, nextTick, reactive, getCurrentInstance } from 'vue';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
import { getHostList, addHost, editHost, deleteHost } from '@/api/modules/host';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import i18n from '@/lang';
|
||||
// import { getHostList, addHost, editHost, deleteHost } from '@/api/modules/host';
|
||||
// import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
// import i18n from '@/lang';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import Terminal from '@/views/terminal/terminal.vue';
|
||||
// import { ElMessage } from 'element-plus';
|
||||
import Terminal from '@/views/terminal/terminal/index.vue';
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const terminalValue = ref();
|
||||
const terminalTabs = ref([]) as any;
|
||||
let tabIndex = 0;
|
||||
const data = ref();
|
||||
const hostDrawer = ref(false);
|
||||
// const data = ref();
|
||||
// const hostDrawer = ref(false);
|
||||
|
||||
let searcConfig = reactive<Host.ReqSearchWithPage>({
|
||||
info: '',
|
||||
page: 1,
|
||||
pageSize: 8,
|
||||
});
|
||||
// let searcConfig = reactive<Host.ReqSearchWithPage>({
|
||||
// info: '',
|
||||
// page: 1,
|
||||
// pageSize: 8,
|
||||
// });
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 8,
|
||||
total: 0,
|
||||
});
|
||||
// const paginationConfig = reactive({
|
||||
// currentPage: 1,
|
||||
// pageSize: 8,
|
||||
// total: 0,
|
||||
// });
|
||||
|
||||
let quickCmd = ref();
|
||||
const quickCmds = [
|
||||
{ label: 'ls', value: 'ls -l' },
|
||||
{ label: 'pwd', value: 'pwd' },
|
||||
];
|
||||
let batchInput = ref();
|
||||
let isBatch = ref<boolean>(false);
|
||||
|
||||
const connVisiable = ref<boolean>(false);
|
||||
const operation = ref();
|
||||
const hover = ref();
|
||||
// const hover = ref();
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const hostInfoRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
@ -222,85 +238,84 @@ const handleTabsEdit = (targetName: string, action: 'remove' | 'add') => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadHost = async () => {
|
||||
searcConfig.page = paginationConfig.currentPage;
|
||||
searcConfig.pageSize = paginationConfig.pageSize;
|
||||
const res = await getHostList(searcConfig);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
// const loadHost = async () => {
|
||||
// searcConfig.page = paginationConfig.currentPage;
|
||||
// searcConfig.pageSize = paginationConfig.pageSize;
|
||||
// const res = await getHostList(searcConfig);
|
||||
// data.value = res.data.items;
|
||||
// paginationConfig.total = res.data.total;
|
||||
// };
|
||||
|
||||
const nextPage = () => {
|
||||
if (paginationConfig.pageSize >= paginationConfig.total) {
|
||||
return;
|
||||
}
|
||||
paginationConfig.pageSize = paginationConfig.pageSize + 3;
|
||||
loadHost();
|
||||
};
|
||||
// const nextPage = () => {
|
||||
// if (paginationConfig.pageSize >= paginationConfig.total) {
|
||||
// return;
|
||||
// }
|
||||
// paginationConfig.pageSize = paginationConfig.pageSize + 3;
|
||||
// loadHost();
|
||||
// };
|
||||
|
||||
function onAddHost() {
|
||||
connVisiable.value = true;
|
||||
operation.value = 'create';
|
||||
if (hostInfoRef.value) {
|
||||
hostInfoRef.value.resetFields();
|
||||
}
|
||||
}
|
||||
// function onAddHost() {
|
||||
// connVisiable.value = true;
|
||||
// operation.value = 'create';
|
||||
// if (hostInfoRef.value) {
|
||||
// hostInfoRef.value.resetFields();
|
||||
// }
|
||||
// }
|
||||
|
||||
function onEditHost(row: Host.Host) {
|
||||
hostInfo.id = row.id;
|
||||
hostInfo.name = row.name;
|
||||
hostInfo.addr = row.addr;
|
||||
hostInfo.port = row.port;
|
||||
hostInfo.user = row.user;
|
||||
hostInfo.authMode = row.authMode;
|
||||
hostInfo.password = '';
|
||||
hostInfo.privateKey = '';
|
||||
operation.value = 'update';
|
||||
connVisiable.value = true;
|
||||
}
|
||||
// function onEditHost(row: Host.Host) {
|
||||
// hostInfo.id = row.id;
|
||||
// hostInfo.name = row.name;
|
||||
// hostInfo.addr = row.addr;
|
||||
// hostInfo.port = row.port;
|
||||
// hostInfo.user = row.user;
|
||||
// hostInfo.authMode = row.authMode;
|
||||
// hostInfo.password = '';
|
||||
// hostInfo.privateKey = '';
|
||||
// operation.value = 'update';
|
||||
// connVisiable.value = true;
|
||||
// }
|
||||
|
||||
const submitAddHost = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
switch (operation.value) {
|
||||
case 'create':
|
||||
await addHost(hostInfo);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
break;
|
||||
case 'update':
|
||||
await editHost(hostInfo);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
break;
|
||||
case 'conn':
|
||||
const res = await addHost(hostInfo);
|
||||
terminalTabs.value.push({
|
||||
key: `${res.data.addr}-${++tabIndex}`,
|
||||
title: res.data.addr,
|
||||
wsID: res.data.id,
|
||||
status: 'online',
|
||||
});
|
||||
terminalValue.value = `${res.data.addr}-${tabIndex}`;
|
||||
}
|
||||
connVisiable.value = false;
|
||||
loadHost();
|
||||
} catch (error) {
|
||||
ElMessage.success(i18n.global.t('commons.msg.loginSuccess') + ':' + error);
|
||||
}
|
||||
});
|
||||
};
|
||||
// const submitAddHost = (formEl: FormInstance | undefined) => {
|
||||
// if (!formEl) return;
|
||||
// formEl.validate(async (valid) => {
|
||||
// if (!valid) return;
|
||||
// try {
|
||||
// switch (operation.value) {
|
||||
// case 'create':
|
||||
// await addHost(hostInfo);
|
||||
// ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
// break;
|
||||
// case 'update':
|
||||
// await editHost(hostInfo);
|
||||
// ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
// break;
|
||||
// case 'conn':
|
||||
// const res = await addHost(hostInfo);
|
||||
// terminalTabs.value.push({
|
||||
// key: `${res.data.addr}-${++tabIndex}`,
|
||||
// title: res.data.addr,
|
||||
// wsID: res.data.id,
|
||||
// status: 'online',
|
||||
// });
|
||||
// terminalValue.value = `${res.data.addr}-${tabIndex}`;
|
||||
// }
|
||||
// connVisiable.value = false;
|
||||
// // loadHost();
|
||||
// } catch (error) {
|
||||
// ElMessage.success(i18n.global.t('commons.msg.loginSuccess') + ':' + error);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
const onConn = (row: Host.Host) => {
|
||||
terminalTabs.value.push({
|
||||
key: `${row.addr}-${++tabIndex}`,
|
||||
title: row.addr,
|
||||
wsID: row.id,
|
||||
status: 'online',
|
||||
});
|
||||
terminalValue.value = `${row.addr}-${tabIndex}`;
|
||||
hostDrawer.value = false;
|
||||
};
|
||||
// const onConn = (row: Host.Host) => {
|
||||
// terminalTabs.value.push({
|
||||
// key: `${row.addr}-${++tabIndex}`,
|
||||
// title: row.addr,
|
||||
// wsID: row.id,
|
||||
// status: 'online',
|
||||
// });
|
||||
// terminalValue.value = `${row.addr}-${tabIndex}`;
|
||||
// };
|
||||
|
||||
const onConnLocal = () => {
|
||||
terminalTabs.value.push({
|
||||
@ -310,14 +325,13 @@ const onConnLocal = () => {
|
||||
status: 'online',
|
||||
});
|
||||
terminalValue.value = `127.0.0.1-${tabIndex}`;
|
||||
hostDrawer.value = false;
|
||||
};
|
||||
|
||||
const onDeleteHost = async (row: Host.Host) => {
|
||||
let ids: Array<number> = [row.id];
|
||||
await useDeleteData(deleteHost, { ids: ids }, 'commons.msg.delete');
|
||||
loadHost();
|
||||
};
|
||||
// const onDeleteHost = async (row: Host.Host) => {
|
||||
// let ids: Array<number> = [row.id];
|
||||
// await useDeleteData(deleteHost, { ids: ids }, 'commons.msg.delete');
|
||||
// loadHost();
|
||||
// };
|
||||
|
||||
function changeFrameHeight() {
|
||||
let ifm = document.getElementById('iframeTerminal') as HTMLInputElement | null;
|
||||
@ -340,7 +354,7 @@ onMounted(() => {
|
||||
changeFrameHeight();
|
||||
window.addEventListener('resize', changeFrameHeight);
|
||||
});
|
||||
loadHost();
|
||||
// loadHost();
|
||||
timer = setInterval(() => {
|
||||
syncTerminal();
|
||||
}, 1000 * 8);
|
||||
@ -367,7 +381,13 @@ onBeforeMount(() => {
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.el-tabs {
|
||||
.terminal-input {
|
||||
&:hover {
|
||||
width: 75%;
|
||||
}
|
||||
width: 25%;
|
||||
}
|
||||
.terminal-tabs {
|
||||
:deep .el-tabs__header {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
@ -406,4 +426,16 @@ onBeforeMount(() => {
|
||||
transition: all 0.15s;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
color: #6b778c;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.el-tabs--right .el-tabs__content,
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user