feat: 完成主机管理界面

pull/16/head
ssongliu 2 years ago committed by ssongliu
parent 89432bc1b8
commit 0fdf519808

@ -9,7 +9,7 @@ import (
)
func (b *BaseApi) CreateGroup(c *gin.Context) {
var req dto.GroupCreate
var req dto.GroupOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -26,17 +26,13 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
}
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 {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := groupService.Delete(req.Name); err != nil {
if err := groupService.Delete(id); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -44,7 +40,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) {
}
func (b *BaseApi) UpdateGroup(c *gin.Context) {
var req dto.GroupUpdate
var req dto.GroupOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -59,17 +55,39 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
return
}
upMap := make(map[string]interface{})
upMap["name"] = req.Name
if err := groupService.Update(id, upMap); err != nil {
if err := groupService.Update(id, req.Name); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) GetGroupInfo(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
group, err := groupService.GetGroupInfo(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, group)
}
func (b *BaseApi) ListGroup(c *gin.Context) {
list, err := groupService.Search()
var req dto.GroupSearch
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
}
list, err := groupService.List(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

@ -5,11 +5,12 @@ import (
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/utils/copier"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) CreateHost(c *gin.Context) {
var req dto.HostCreate
var req dto.HostOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -42,6 +43,25 @@ func (b *BaseApi) HostTree(c *gin.Context) {
helper.SuccessWithData(c, data)
}
func (b *BaseApi) GetHostInfo(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
host, err := hostService.GetHostInfo(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
var hostDto dto.HostInfo
if err := copier.Copy(&hostDto, host); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, hostDto)
}
func (b *BaseApi) DeleteHost(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {
@ -61,7 +81,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
}
func (b *BaseApi) UpdateHost(c *gin.Context) {
var req dto.HostUpdate
var req dto.HostOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -78,7 +98,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap := make(map[string]interface{})
upMap["name"] = req.Name
upMap["group"] = req.Group
upMap["group_belong"] = req.GroupBelong
upMap["addr"] = req.Addr
upMap["port"] = req.Port
upMap["user"] = req.User

@ -32,7 +32,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
host, err := hostService.GetConnInfo(uint(id))
host, err := hostService.GetHostInfo(uint(id))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return

@ -10,6 +10,7 @@ type CommandUpdate struct {
}
type CommandInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Command string `json:"command"`
}

@ -2,7 +2,7 @@ package dto
type SearchWithPage struct {
PageInfo
Name string `json:"name" validate:"required"`
Info string `json:"info" validate:"required"`
}
type PageInfo struct {

@ -1,10 +1,16 @@
package dto
type GroupCreate struct {
type GroupOperate struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
}
type GroupUpdate struct {
Name string `json:"name" validate:"required"`
type GroupSearch struct {
Type string `json:"type" validate:"required"`
}
type GroupInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
}

@ -4,15 +4,15 @@ 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"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
PrivateKey string `json:"privateKey"`
Password string `json:"password"`
type HostOperate struct {
GroupBelong string `json:"groupBelong" 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"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
PrivateKey string `json:"privateKey"`
Password string `json:"password"`
Description string `json:"description"`
}
@ -22,36 +22,25 @@ type SearchForTree struct {
}
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"`
User string `json:"user"`
AuthMode string `json:"authMode"`
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
GroupBelong string `json:"groupBelong"`
Name string `json:"name"`
Addr string `json:"addr"`
Port uint `json:"port"`
User string `json:"user"`
AuthMode string `json:"authMode"`
Description string `json:"description"`
}
type HostTree struct {
ID uint `json:"id"`
Label string `json:"label"`
Children []TreeChild `json:"children"`
}
type TreeChild struct {
ID uint `json:"id"`
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"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
PrivateKey string `json:"privateKey"`
Password string `json:"password"`
Description string `json:"description"`
}

@ -4,6 +4,6 @@ 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"`
Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
Command string `gorm:"type:varchar(256);not null" json:"command"`
}

@ -5,5 +5,5 @@ 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"`
Type string `gorm:"type:varchar(16);not null" json:"type"`
}

@ -4,14 +4,14 @@ 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"`
User string `gorm:"type:varchar(64);not null" json:"user"`
AuthMode string `gorm:"type:varchar(16);not null" json:"authMode"`
Password string `gorm:"type:varchar(64)" json:"password"`
PrivateKey string `gorm:"type:varchar(256)" json:"privateKey"`
GroupBelong string `gorm:"type:varchar(64);not null" json:"groupBelong"`
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"`
User string `gorm:"type:varchar(64);not null" json:"user"`
AuthMode string `gorm:"type:varchar(16);not null" json:"authMode"`
Password string `gorm:"type:varchar(64)" json:"password"`
PrivateKey string `gorm:"type:varchar(256)" json:"privateKey"`
Description string `gorm:"type:varchar(256)" json:"description"`
}

@ -3,6 +3,7 @@ package repo
import (
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/global"
"gorm.io/gorm"
)
type GroupRepo struct{}
@ -10,6 +11,7 @@ type GroupRepo struct{}
type IGroupRepo interface {
Get(opts ...DBOption) (model.Group, error)
GetList(opts ...DBOption) ([]model.Group, error)
WithByType(groupType string) DBOption
Create(group *model.Group) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
@ -39,6 +41,12 @@ func (u *GroupRepo) GetList(opts ...DBOption) ([]model.Group, error) {
return groups, err
}
func (c *GroupRepo) WithByType(groupType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("type = ?", groupType)
}
}
func (u *GroupRepo) Create(group *model.Group) error {
return global.DB.Create(group).Error
}

@ -13,6 +13,7 @@ type IHostRepo interface {
GetList(opts ...DBOption) ([]model.Host, error)
WithByInfo(info string) DBOption
Create(host *model.Host) error
ChangeGroup(oldGroup, newGroup string) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
}
@ -55,6 +56,10 @@ func (u *HostRepo) Create(host *model.Host) error {
return global.DB.Create(host).Error
}
func (u *HostRepo) ChangeGroup(oldGroup, newGroup string) error {
return global.DB.Model(&model.Host{}).Where("group_belong = ?", oldGroup).Updates(map[string]interface{}{"group_belong": newGroup}).Error
}
func (u *HostRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error
}

@ -31,7 +31,7 @@ func (u *CommandService) Search() ([]model.Command, error) {
}
func (u *CommandService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Name))
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
var dtoCommands []dto.CommandInfo
for _, command := range commands {
var item dto.CommandInfo

@ -2,7 +2,6 @@ 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"
@ -11,25 +10,46 @@ import (
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
GetGroupInfo(id uint) (*dto.GroupInfo, error)
List(req dto.GroupSearch) ([]dto.GroupInfo, error)
Create(groupDto dto.GroupOperate) error
Update(id uint, name string) error
Delete(id uint) error
}
func NewIGroupService() IGroupService {
return &GroupService{}
}
func (u *GroupService) Search() ([]model.Group, error) {
groups, err := groupRepo.GetList()
func (u *GroupService) GetGroupInfo(id uint) (*dto.GroupInfo, error) {
group, err := groupRepo.Get(commonRepo.WithByID(id))
if err != nil {
return nil, constant.ErrRecordNotFound
}
return groups, err
var dtoGroup dto.GroupInfo
if err := copier.Copy(&dtoGroup, &group); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return &dtoGroup, err
}
func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) {
groups, err := groupRepo.GetList(groupRepo.WithByType(req.Type))
if err != nil {
return nil, constant.ErrRecordNotFound
}
var dtoUsers []dto.GroupInfo
for _, group := range groups {
var item dto.GroupInfo
if err := copier.Copy(&item, &group); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoUsers = append(dtoUsers, item)
}
return dtoUsers, err
}
func (u *GroupService) Create(groupDto dto.GroupCreate) error {
func (u *GroupService) Create(groupDto dto.GroupOperate) error {
group, _ := groupRepo.Get(commonRepo.WithByName(groupDto.Name), commonRepo.WithByName(groupDto.Name))
if group.ID != 0 {
return constant.ErrRecordExist
@ -43,14 +63,27 @@ func (u *GroupService) Create(groupDto dto.GroupCreate) error {
return nil
}
func (u *GroupService) Delete(name string) error {
group, _ := groupRepo.Get(commonRepo.WithByName(name))
func (u *GroupService) Delete(id uint) error {
group, _ := groupRepo.Get(commonRepo.WithByID(id))
if group.ID == 0 {
return constant.ErrRecordNotFound
}
return groupRepo.Delete(commonRepo.WithByID(group.ID))
if err := hostRepo.ChangeGroup(group.Name, "default"); err != nil {
return err
}
return groupRepo.Delete(commonRepo.WithByID(id))
}
func (u *GroupService) Update(id uint, upMap map[string]interface{}) error {
func (u *GroupService) Update(id uint, name string) error {
group, _ := groupRepo.Get(commonRepo.WithByID(id))
if group.ID == 0 {
return constant.ErrRecordNotFound
}
upMap := make(map[string]interface{})
upMap["name"] = name
if err := hostRepo.ChangeGroup(group.Name, name); err != nil {
return err
}
return groupRepo.Update(id, upMap)
}

@ -13,9 +13,9 @@ import (
type HostService struct{}
type IHostService interface {
GetConnInfo(id uint) (*model.Host, error)
GetHostInfo(id uint) (*model.Host, error)
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
Create(hostDto dto.HostCreate) (*dto.HostInfo, error)
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) error
BatchDelete(ids []uint) error
}
@ -24,7 +24,7 @@ func NewIHostService() IHostService {
return &HostService{}
}
func (u *HostService) GetConnInfo(id uint) (*model.Host, error) {
func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
host, err := hostRepo.Get(commonRepo.WithByID(id))
if err != nil {
return nil, constant.ErrRecordNotFound
@ -34,26 +34,30 @@ func (u *HostService) GetConnInfo(id uint) (*model.Host, error) {
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 {
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))
}
if err != nil {
return nil, err
}
groups, err := groupRepo.GetList()
if err != nil {
return nil, 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})
var datas []dto.HostTree
for _, group := range groups {
var data dto.HostTree
data.ID = group.ID + 10000
data.Label = group.Name
for _, host := range hosts {
label := fmt.Sprintf("%s@%s:%d", host.User, host.Addr, host.Port)
if host.GroupBelong == group.Name {
data.Children = append(data.Children, dto.TreeChild{ID: host.ID, Label: label})
}
}
data = append(data, dto.HostTree{Label: key, Children: children})
datas = append(datas, data)
}
return data, err
return datas, err
}
func (u *HostService) Create(hostDto dto.HostCreate) (*dto.HostInfo, error) {
func (u *HostService) Create(hostDto dto.HostOperate) (*dto.HostInfo, error) {
host, _ := hostRepo.Get(commonRepo.WithByName(hostDto.Name))
if host.ID != 0 {
return nil, constant.ErrRecordExist

@ -44,7 +44,7 @@ func (u *UserService) Get(id uint) (*dto.UserInfo, error) {
}
func (u *UserService) Page(search dto.SearchWithPage) (int64, interface{}, error) {
total, users, err := userRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Name))
total, users, err := userRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
var dtoUsers []dto.UserInfo
for _, user := range users {
var item dto.UserInfo

@ -35,6 +35,15 @@ var AddTableOperationLog = &gormigrate.Migration{
var AddTableHost = &gormigrate.Migration{
ID: "20200818-add-table-host",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Host{})
if err := tx.AutoMigrate(&model.Host{}); err != nil {
return err
}
if err := tx.AutoMigrate(&model.Group{}); err != nil {
return err
}
if err := tx.AutoMigrate(&model.Command{}); err != nil {
return err
}
return nil
},
}

@ -40,6 +40,8 @@ func Routers() *gin.Engine {
systemRouter.InitBaseRouter(PrivateGroup)
systemRouter.InitUserRouter(PrivateGroup)
systemRouter.InitHostRouter(PrivateGroup)
systemRouter.InitGroupRouter(PrivateGroup)
systemRouter.InitCommandRouter(PrivateGroup)
systemRouter.InitTerminalRouter(PrivateGroup)
systemRouter.InitOperationLogRouter(PrivateGroup)
}

@ -4,6 +4,8 @@ type RouterGroup struct {
BaseRouter
UserRouter
HostRouter
GroupRouter
CommandRouter
OperationLogRouter
}

@ -1,23 +0,0 @@
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)
}
}

@ -17,8 +17,8 @@ func (s *CommandRouter) InitCommandRouter(Router *gin.RouterGroup) {
{
withRecordRouter.POST("", baseApi.CreateCommand)
withRecordRouter.POST("/del", baseApi.DeleteCommand)
withRecordRouter.PUT(":id", baseApi.UpdateCommand)
userRouter.POST("/search", baseApi.SearchCommand)
userRouter.GET("", baseApi.ListCommand)
userRouter.PUT(":id", baseApi.UpdateCommand)
}
}

@ -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("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateGroup)
withRecordRouter.DELETE(":id", baseApi.DeleteGroup)
userRouter.POST("/search", baseApi.ListGroup)
userRouter.GET(":id", baseApi.GetGroupInfo)
userRouter.PUT(":id", baseApi.UpdateGroup)
}
}

@ -10,14 +10,14 @@ import (
type HostRouter struct{}
func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
userRouter := Router.Group("hosts")
userRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := userRouter.Use(middleware.OperationRecord())
hostRouter := Router.Group("hosts").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("hosts").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateHost)
withRecordRouter.POST("/del", baseApi.DeleteHost)
userRouter.POST("/search", baseApi.HostTree)
userRouter.PUT(":id", baseApi.UpdateHost)
withRecordRouter.DELETE(":id", baseApi.DeleteHost)
hostRouter.POST("/search", baseApi.HostTree)
hostRouter.GET(":id", baseApi.GetHostInfo)
hostRouter.PUT(":id", baseApi.UpdateHost)
}
}

@ -0,0 +1,17 @@
import { ReqPage } from '.';
export namespace Command {
export interface CommandInfo {
id: number;
name: string;
command: string;
}
export interface CommandOperate {
id: number;
name: string;
command: string;
}
export interface CommandSearch extends ReqPage {
info: string;
}
}

@ -0,0 +1,15 @@
export namespace Group {
export interface GroupInfo {
id: number;
name: string;
type: string;
}
export interface GroupOperate {
id: number;
name: string;
type: string;
}
export interface GroupSearch {
type: string;
}
}

@ -2,11 +2,17 @@ import { CommonModel } from '.';
export namespace Host {
export interface HostTree {
id: number;
label: string;
children: Array<TreeNode>;
}
export interface TreeNode {
id: number;
label: string;
children: Array<string>;
}
export interface Host extends CommonModel {
name: string;
groupBelong: string;
addr: string;
port: number;
user: string;
@ -16,6 +22,7 @@ export namespace Host {
export interface HostOperate {
id: number;
name: string;
groupBelong: string;
addr: string;
port: number;
user: string;

@ -12,7 +12,7 @@ export namespace User {
}
export interface ReqGetUserParams extends ReqPage {
name?: string;
info?: string;
email?: string;
}
}

@ -0,0 +1,24 @@
import http from '@/api';
import { ResPage } from '../interface';
import { Command } from '../interface/command';
export const getCommandList = () => {
return http.get<Array<Command.CommandInfo>>(`/commands`, {});
};
export const getCommandPage = (params: Command.CommandSearch) => {
return http.post<ResPage<Command.CommandInfo>>(`/commands/search`, params);
};
export const addCommand = (params: Command.CommandOperate) => {
return http.post<Command.CommandOperate>(`/commands`, params);
};
export const editCommand = (params: Command.CommandOperate) => {
console.log(params.id);
return http.put(`/commands/${params.id}`, params);
};
export const deleteCommand = (params: { ids: number[] }) => {
return http.post(`/commands/del`, params);
};

@ -0,0 +1,18 @@
import http from '@/api';
import { Group } from '../interface/group';
export const getGroupList = (params: Group.GroupSearch) => {
return http.post<Array<Group.GroupInfo>>(`/groups/search`, params);
};
export const addGroup = (params: Group.GroupOperate) => {
return http.post<Group.GroupOperate>(`/groups`, params);
};
export const editGroup = (params: Group.GroupOperate) => {
return http.put(`/groups/` + params.id, params);
};
export const deleteGroup = (id: number) => {
return http.delete(`/groups/` + id);
};

@ -5,6 +5,10 @@ export const getHostList = (params: Host.ReqSearch) => {
return http.post<Array<Host.HostTree>>(`/hosts/search`, params);
};
export const getHostInfo = (id: number) => {
return http.get<Host.Host>(`/hosts/` + id);
};
export const addHost = (params: Host.HostOperate) => {
return http.post<Host.HostOperate>(`/hosts`, params);
};
@ -14,6 +18,6 @@ export const editHost = (params: Host.HostOperate) => {
return http.put(`/hosts/` + params.id, params);
};
export const deleteHost = (params: { ids: number[] }) => {
return http.post(`/hosts/del`, params);
export const deleteHost = (id: number) => {
return http.delete(`/hosts/` + id);
};

@ -13,11 +13,13 @@ export default {
},
table: {
name: '',
group: '组',
createdAt: '',
date: '',
updatedAt: '',
operate: '',
message: '',
description: '',
},
msg: {
delete: ',',
@ -94,6 +96,7 @@ export default {
conn: '',
hostList: '',
quickCmd: '',
command: '',
addHost: '',
localhost: '',
name: '',
@ -110,6 +113,8 @@ export default {
detail: {
users: '',
hosts: '',
groups: '组',
command: '',
auth: '',
post: '',
put: '',

@ -92,7 +92,7 @@ const search = async () => {
};
const fmtOperation = (row: ResOperationLog) => {
if (row.method.toLocaleLowerCase() !== 'put') {
if (row.method.toLocaleLowerCase() === 'post') {
if (row.source == '' && row.action == '') {
return (
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
@ -118,7 +118,6 @@ const fmtOperation = (row: ResOperationLog) => {
i18n.global.t('operations.detail.' + row.source.toLocaleLowerCase())
);
}
return '';
};
const fmtBody = (value: string) => {

@ -0,0 +1,154 @@
<template>
<div>
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
<template #toolbar>
<el-button @click="onCreate()">{{ $t('commons.button.create') }}</el-button>
<el-button type="danger" plain :disabled="selects.length === 0" @click="batchDelete(null)">{{
$t('commons.button.delete')
}}</el-button>
</template>
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix />
<el-table-column :label="$t('terminal.command')" min-width="300" show-overflow-tooltip prop="command" />
<fu-table-operations type="icon" :buttons="buttons" :label="$t('commons.table.operate')" fix />
</ComplexTable>
<el-dialog v-model="cmdVisiable" :title="$t('terminal.addHost')" width="30%">
<el-form ref="commandInfoRef" label-width="100px" label-position="left" :model="commandInfo" :rules="rules">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="commandInfo.name" />
</el-form-item>
<el-form-item :label="$t('terminal.command')" prop="command">
<el-input type="textarea" clearable v-model="commandInfo.command" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="cmdVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submitAddCommand(commandInfoRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import ComplexTable from '@/components/complex-table/index.vue';
import { Command } from '@/api/interface/command';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/command';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { useDeleteData } from '@/hooks/use-delete-data';
import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rues';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
const data = ref();
const selects = ref<any>([]);
const paginationConfig = reactive({
page: 1,
pageSize: 5,
total: 0,
});
const commandSearch = reactive({
page: 1,
pageSize: 5,
info: '',
});
type FormInstance = InstanceType<typeof ElForm>;
const commandInfoRef = ref<FormInstance>();
const rules = reactive({
name: [Rules.requiredInput],
command: [Rules.requiredInput],
});
let operate = ref<string>('create');
let commandInfo = reactive<Command.CommandOperate>({
id: 0,
name: '',
command: '',
});
const cmdVisiable = ref<boolean>(false);
const onCreate = async () => {
restcommandForm();
operate.value = 'create';
cmdVisiable.value = true;
};
const submitAddCommand = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
console.log(commandInfo.id);
if (operate.value === 'create') {
await addCommand(commandInfo);
} else {
await editCommand(commandInfo);
}
cmdVisiable.value = false;
search();
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
});
};
const onEdit = async (row: Command.CommandInfo | null) => {
if (row !== null) {
console.log(row.id);
// commandInfo.id = row.id;
// commandInfo.name = row.name;
// commandInfo.command = row.command;
// operate.value = 'edit';
// cmdVisiable.value = true;
}
};
const batchDelete = async (row: Command.CommandInfo | null) => {
let ids: Array<number> = [];
if (row === null) {
selects.value.forEach((item: Command.CommandInfo) => {
ids.push(item.id);
});
} else {
ids.push(row.id);
}
await useDeleteData(deleteCommand, { ids: ids }, 'commons.msg.delete');
search();
};
function restcommandForm() {
if (commandInfoRef.value) {
commandInfoRef.value.resetFields();
}
}
const buttons = [
{
label: i18n.global.t('commons.button.edit'),
icon: 'Edit',
click: onEdit,
},
{
label: i18n.global.t('commons.button.delete'),
icon: 'Delete',
click: batchDelete,
},
];
const search = async () => {
commandSearch.page = paginationConfig.page;
commandSearch.pageSize = paginationConfig.pageSize;
const res = await getCommandPage(commandSearch);
data.value = res.data.items;
for (const d of data.value) {
d.id = d.id + '';
}
paginationConfig.total = res.data.total;
};
onMounted(() => {
search();
});
</script>

@ -2,22 +2,64 @@
<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-tooltip class="box-item" effect="dark" content="创建连接" placement="top-start">
<el-button icon="Plus" @click="restHostForm" size="small" />
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="创建分组" placement="top-start">
<el-button icon="FolderAdd" @click="onGroupCreate" size="small" />
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="展开" placement="top-start">
<el-button icon="Expand" @click="setTreeStatus(true)" size="small" />
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="收缩" placement="top-start">
<el-button icon="Fold" @click="setTreeStatus(false)" size="small" />
</el-tooltip>
<el-input
size="small"
@input="loadHostTree"
clearable
style="margin-top: 5px"
v-model="searcConfig.info"
>
<template #append><el-button icon="search" @click="loadHostTree" /></template>
</el-input>
<el-input size="small" v-if="folderCreate" clearable style="margin-top: 5px" v-model="newGroupName">
<el-input
size="small"
v-if="groupInputShow"
clearable
style="margin-top: 5px"
v-model="groupInputValue"
>
<template #append>
<el-button-group>
<el-button icon="Check" @click="loadHost" />
<el-button icon="Close" @click="folderCreate = false" />
<el-button icon="Check" @click="onCreateGroup" />
<el-button icon="Close" @click="groupInputShow = false" />
</el-button-group>
</template>
</el-input>
<el-tree ref="tree" :default-expand-all="true" :data="hostTree" :props="defaultProps" />
<el-tree
ref="tree"
:expand-on-click-node="false"
node-key="id"
:default-expand-all="true"
:data="hostTree"
:props="defaultProps"
draggable
>
<template #default="{ node, data }">
<span class="custom-tree-node" @mouseover="hover = data.id" @mouseleave="hover = null">
<span>
<a @click="onEdit(node, data)">{{ node.label }}</a>
</span>
<el-button-group
v-if="!(node.level === 1 && data.label === 'default') && data.id === hover"
>
<el-button icon="Edit" size="small" @click="onEdit(node, data)" />
<el-button icon="Delete" size="small" @click="onDelete(node, data)" />
</el-button-group>
</span>
</template>
</el-tree>
</el-card>
</el-col>
<el-col :span="16">
@ -26,6 +68,11 @@
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="hostInfo.name" />
</el-form-item>
<el-form-item :label="$t('commons.table.group')" prop="groupBelong">
<el-select v-model="hostInfo.groupBelong" clearable style="width: 100%">
<el-option v-for="item in groupList" :key="item.id" :label="item.name" :value="item.name" />
</el-select>
</el-form-item>
<el-form-item label="IP" prop="addr">
<el-input clearable v-model="hostInfo.addr" />
</el-form-item>
@ -51,10 +98,19 @@
<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 :label="$t('commons.table.description')" prop="description">
<el-input clearable type="textarea" v-model="hostInfo.description" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitAddHost(hostInfoRef)">
<el-button @click="restHostForm">
{{ $t('commons.button.reset') }}
</el-button>
<el-button v-if="hostOperation === 'create'" type="primary" @click="submitAddHost(hostInfoRef)">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-if="hostOperation === 'edit'" type="primary" @click="submitAddHost(hostInfoRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</el-form-item>
</el-form>
</el-card>
@ -67,14 +123,19 @@ 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 { Group } from '@/api/interface/group';
import { getHostList, getHostInfo, addHost, editHost, deleteHost } from '@/api/modules/host';
import { getGroupList, addGroup, editGroup, deleteGroup } from '@/api/modules/group';
import { useDeleteData } from '@/hooks/use-delete-data';
import { ElMessage } from 'element-plus';
import i18n from '@/lang';
import type Node from 'element-plus/es/components/tree/src/model/node';
type FormInstance = InstanceType<typeof ElForm>;
const hostInfoRef = ref<FormInstance>();
const rules = reactive({
name: [Rules.requiredInput, Rules.name],
group: [Rules.requiredSelect],
addr: [Rules.requiredInput, Rules.ip],
port: [Rules.requiredInput, Rules.port],
user: [Rules.requiredInput],
@ -82,10 +143,11 @@ const rules = reactive({
password: [Rules.requiredInput],
privateKey: [Rules.requiredInput],
});
let hostOperation = ref<string>('create');
let hostInfo = reactive<Host.HostOperate>({
id: 0,
name: '',
groupBelong: 'default',
addr: '',
port: 22,
user: '',
@ -95,30 +157,47 @@ let hostInfo = reactive<Host.HostOperate>({
description: '',
});
interface Tree {
id: number;
label: string;
children?: Tree[];
}
let searcConfig = reactive<Host.ReqSearch>({
info: '',
});
const tree = ref<any>(null);
const hover = ref();
const hostTree = ref<Array<Host.HostTree>>();
const defaultProps = {
children: 'children',
label: 'label',
children: 'children',
};
const newGroupName = ref();
const folderCreate = ref<boolean>(false);
const loadHost = async () => {
const groupList = ref<Array<Group.GroupInfo>>();
let groupInputValue = ref();
let currentGroupID = ref();
let groupOperation = ref<string>('create');
let groupInputShow = ref<boolean>(false);
const loadHostTree = async () => {
const res = await getHostList(searcConfig);
hostTree.value = res.data;
};
const loadGroups = async () => {
const res = await getGroupList({ type: 'host' });
groupList.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() {
function restHostForm() {
if (hostInfoRef.value) {
hostInfoRef.value.resetFields();
}
@ -128,17 +207,92 @@ const submitAddHost = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
if (hostOperation.value === 'create') {
await addHost(hostInfo);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
// loadHost();
} catch (error) {
ElMessage.success(i18n.global.t('commons.msg.loginSuccess') + ':' + error);
} else {
await editHost(hostInfo);
}
restHostForm();
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
loadHostTree();
});
};
const onGroupCreate = () => {
groupInputShow.value = true;
groupInputValue.value = '';
groupOperation.value = 'create';
};
const onCreateGroup = async () => {
console.log(groupOperation.value);
if (groupOperation.value === 'create') {
let group = { id: 0, name: groupInputValue.value, type: 'host' };
await addGroup(group);
groupOperation.value = '';
groupInputShow.value = false;
} else {
let group = { id: currentGroupID.value, name: groupInputValue.value, type: 'host' };
await editGroup(group);
}
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
groupOperation.value = '';
groupInputShow.value = false;
loadHostTree();
loadGroups();
};
const onDelete = async (node: Node, data: Tree) => {
if (node.level === 1 && data.label === 'default') {
return;
}
if (node.level === 1) {
await useDeleteData(deleteGroup, data.id - 10000, '移除组后,组内所有连接将迁移到 default 组内,是否确认?');
loadGroups();
} else {
await useDeleteData(deleteHost, data.id, 'commons.msg.delete');
}
loadHostTree();
loadGroups();
};
const onEdit = async (node: Node, data: Tree) => {
if (node.level === 1 && data.label === 'default') {
return;
}
console.log(node.level === 1);
if (node.level === 1) {
groupInputShow.value = true;
groupInputValue.value = data.label;
currentGroupID.value = data.id - 10000;
groupOperation.value = 'edit';
console.log(groupOperation.value);
return;
} else {
const res = await getHostInfo(data.id);
hostInfo.id = res.data.id;
hostInfo.name = res.data.name;
hostInfo.groupBelong = res.data.groupBelong;
hostInfo.addr = res.data.addr;
hostInfo.port = res.data.port;
hostInfo.user = res.data.user;
hostInfo.description = res.data.description;
hostOperation.value = 'edit';
}
};
onMounted(() => {
loadHost();
loadHostTree();
loadGroups();
});
</script>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>

Loading…
Cancel
Save