diff --git a/backend/app/api/v1/host.go b/backend/app/api/v1/host.go index 623382c32..155f2d73c 100644 --- a/backend/app/api/v1/host.go +++ b/backend/app/api/v1/host.go @@ -95,7 +95,7 @@ func (b *BaseApi) TestByID(c *gin.Context) { // @Param request body dto.SearchForTree true "request" // @Success 200 {anrry} dto.HostTree // @Security ApiKeyAuth -// @Router /hosts/search [post] +// @Router /hosts/tree [post] func (b *BaseApi) HostTree(c *gin.Context) { var req dto.SearchForTree if err := c.ShouldBindJSON(&req); err != nil { @@ -112,6 +112,33 @@ func (b *BaseApi) HostTree(c *gin.Context) { helper.SuccessWithData(c, data) } +// @Tags Host +// @Summary Page host +// @Description 获取主机列表分页 +// @Accept json +// @Param request body dto.SearchHostWithPage true "request" +// @Success 200 {anrry} dto.HostTree +// @Security ApiKeyAuth +// @Router /hosts/search [post] +func (b *BaseApi) SearchHost(c *gin.Context) { + var req dto.SearchHostWithPage + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + total, list, err := hostService.SearchWithPage(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + // @Tags Host // @Summary Load host info // @Description 加载主机信息 @@ -143,13 +170,13 @@ func (b *BaseApi) GetHostInfo(c *gin.Context) { // @Summary Delete host // @Description 删除主机 // @Accept json -// @Param request body dto.OperateByID true "request" +// @Param request body dto.BatchDeleteReq true "request" // @Success 200 // @Security ApiKeyAuth // @Router /hosts/del [post] -// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"hosts","output_colume":"addr","output_value":"addr"}],"formatZH":"删除主机 [addr]","formatEN":"delete host [addr]"} +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"hosts","output_colume":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"} func (b *BaseApi) DeleteHost(c *gin.Context) { - var req dto.OperateByID + var req dto.BatchDeleteReq if err := c.ShouldBindJSON(&req); err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return @@ -159,7 +186,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) { return } - if err := hostService.Delete(req.ID); err != nil { + if err := hostService.Delete(req.Ids); err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } @@ -202,3 +229,32 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { } helper.SuccessWithData(c, nil) } + +// @Tags Host +// @Summary Update host group +// @Description 切换分组 +// @Accept json +// @Param request body dto.ChangeHostGroup true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /hosts/update [post] +// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"hosts","output_colume":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"} +func (b *BaseApi) UpdateHostGroup(c *gin.Context) { + var req dto.ChangeHostGroup + 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 + } + + upMap := make(map[string]interface{}) + upMap["group_belong"] = req.Group + if err := hostService.Update(req.ID, upMap); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/host.go b/backend/app/dto/host.go index 2ed1d929f..8f07e3101 100644 --- a/backend/app/dto/host.go +++ b/backend/app/dto/host.go @@ -27,10 +27,21 @@ type HostConnTest struct { Password string `json:"password"` } +type SearchHostWithPage struct { + PageInfo + Group string `json:"group"` + Info string `json:"info"` +} + type SearchForTree struct { Info string `json:"info"` } +type ChangeHostGroup struct { + ID uint `json:"id" validate:"required"` + Group string `json:"group" validate:"required"` +} + type HostInfo struct { ID uint `json:"id"` CreatedAt time.Time `json:"createdAt"` diff --git a/backend/app/repo/host.go b/backend/app/repo/host.go index de4d53bdd..8a5389d31 100644 --- a/backend/app/repo/host.go +++ b/backend/app/repo/host.go @@ -11,10 +11,12 @@ type HostRepo struct{} type IHostRepo interface { Get(opts ...DBOption) (model.Host, error) GetList(opts ...DBOption) ([]model.Host, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error) WithByInfo(info string) DBOption WithByPort(port uint) DBOption WithByUser(user string) DBOption WithByAddr(addr string) DBOption + WithByGroup(group string) DBOption Create(host *model.Host) error ChangeGroup(oldGroup, newGroup string) error Update(id uint, vars map[string]interface{}) error @@ -45,6 +47,18 @@ func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { return hosts, err } +func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) { + var users []model.Host + db := global.DB.Model(&model.Host{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + func (c *HostRepo) WithByInfo(info string) DBOption { return func(g *gorm.DB) *gorm.DB { if len(info) == 0 { @@ -70,6 +84,14 @@ func (u *HostRepo) WithByAddr(addr string) DBOption { return g.Where("addr = ?", addr) } } +func (u *HostRepo) WithByGroup(group string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(group) == 0 { + return g + } + return g.Where("group_belong = ?", group) + } +} func (u *HostRepo) Create(host *model.Host) error { return global.DB.Create(host).Error diff --git a/backend/app/service/host.go b/backend/app/service/host.go index a313351f6..81e9f0656 100644 --- a/backend/app/service/host.go +++ b/backend/app/service/host.go @@ -17,9 +17,10 @@ type IHostService interface { TestLocalConn(id uint) bool GetHostInfo(id uint) (*model.Host, error) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) + SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) Create(hostDto dto.HostOperate) (*dto.HostInfo, error) Update(id uint, upMap map[string]interface{}) error - Delete(id uint) error + Delete(id []uint) error } func NewIHostService() IHostService { @@ -63,6 +64,22 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) { return &host, err } +func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) { + total, hosts, err := hostRepo.Page(search.Page, search.PageSize, hostRepo.WithByInfo(search.Info), hostRepo.WithByGroup(search.Group)) + if err != nil { + return 0, nil, err + } + var dtoHosts []dto.HostInfo + 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()) + } + dtoHosts = append(dtoHosts, item) + } + return total, dtoHosts, err +} + func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) { hosts, err := hostRepo.GetList(hostRepo.WithByInfo(search.Info)) if err != nil { @@ -136,15 +153,17 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { return &hostinfo, nil } -func (u *HostService) Delete(id uint) error { - host, _ := hostRepo.Get(commonRepo.WithByID(id)) - if host.ID == 0 { - return constant.ErrRecordNotFound +func (u *HostService) Delete(ids []uint) error { + hosts, _ := hostRepo.GetList(commonRepo.WithIdsIn(ids)) + for _, host := range hosts { + if host.ID == 0 { + return constant.ErrRecordNotFound + } + if host.Addr == "127.0.0.1" { + return errors.New("the local connection information cannot be deleted!") + } } - if host.Addr == "127.0.0.1" { - return errors.New("the local connection information cannot be deleted!") - } - return hostRepo.Delete(commonRepo.WithByID(id)) + return hostRepo.Delete(commonRepo.WithIdsIn(ids)) } func (u *HostService) Update(id uint, upMap map[string]interface{}) error { diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index 065d6f54c..f0439a256 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -19,7 +19,9 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("", baseApi.CreateHost) hostRouter.POST("/del", baseApi.DeleteHost) hostRouter.POST("/update", baseApi.UpdateHost) - hostRouter.POST("/search", baseApi.HostTree) + hostRouter.POST("/update/group", baseApi.UpdateHostGroup) + hostRouter.POST("/search", baseApi.SearchHost) + hostRouter.POST("/tree", baseApi.HostTree) hostRouter.POST("/test/byinfo", baseApi.TestByInfo) hostRouter.POST("/test/byid/:id", baseApi.TestByID) hostRouter.GET(":id", baseApi.GetHostInfo) diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 2f0e256a4..4023dbae6 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -1,4 +1,4 @@ -import { CommonModel } from '.'; +import { CommonModel, ReqPage } from '.'; export namespace Host { export interface HostTree { @@ -40,7 +40,15 @@ export namespace Host { privateKey: string; password: string; } + export interface GroupChange { + id: number; + group: string; + } export interface ReqSearch { info?: string; } + export interface SearchWithPage extends ReqPage { + group: string; + info?: string; + } } diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 219dade4f..8e7997f1d 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -4,8 +4,11 @@ import { Command } from '../interface/command'; import { Group } from '../interface/group'; import { Host } from '../interface/host'; +export const searchHosts = (params: Host.SearchWithPage) => { + return http.post>(`/hosts/search`, params); +}; export const getHostTree = (params: Host.ReqSearch) => { - return http.post>(`/hosts/search`, params); + return http.post>(`/hosts/tree`, params); }; export const getHostInfo = (id: number) => { return http.get(`/hosts/` + id); @@ -22,8 +25,11 @@ export const testByID = (id: number) => { export const editHost = (params: Host.HostOperate) => { return http.post(`/hosts/update`, params); }; -export const deleteHost = (id: number) => { - return http.post(`/hosts/del`, { id: id }); +export const editHostGroup = (params: Host.GroupChange) => { + return http.post(`/hosts/update/group`, params); +}; +export const deleteHost = (params: { ids: number[] }) => { + return http.post(`/hosts/del`, params); }; // group diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts index d26e8c085..5844246be 100644 --- a/frontend/src/global/form-rules.ts +++ b/frontend/src/global/form-rules.ts @@ -19,7 +19,7 @@ const complexityPassword = (rule: any, value: any, callback: any) => { if (value === '' || typeof value === 'undefined' || value == null) { callback(new Error(i18n.global.t('commons.rule.complexityPassword'))); } else { - const reg = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[~!@#$%^&*.])[\da-zA-Z~!@#$%^&*.]{8,}$/; + const reg = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[~!@#$%^&*.-_])[\da-zA-Z~!@#$%^&*.-_]{8,}$/; if (!reg.test(value) && value !== '') { callback(new Error(i18n.global.t('commons.rule.complexityPassword'))); } else { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index b5a86ee03..77cd65e80 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -126,7 +126,7 @@ export default { imageName: 'Support English, Chinese, numbers, :.-_, length 1-30', volumeName: 'Support English, numbers, .-_, length 1-30', complexityPassword: - 'Please enter a password with more than 8 characters and must contain letters, digits, and special symbols', + 'Please enter a password with more than 8 characters and must contain letters, digits, and special symbols(~!@#$%^&*.-_)', commonPassword: 'Please enter a password with more than 6 characters', email: 'Email format error', number: 'Please enter the correct number', @@ -606,9 +606,10 @@ export default { saveAndConn: 'Save and Connect', connTestOk: 'Connection information available', connTestFailed: 'Connection unavailable, please check connection information!', - hostList: 'Host information', + host: 'Host', createConn: 'Create a connection', - createGroup: 'Create a group', + group: 'Group', + groupChange: 'Change group', expand: 'Expand all', fold: 'All contract', batchInput: 'Batch input', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index dee7ec582..7f4983c0d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -130,7 +130,7 @@ export default { dbName: '支持英文、中文、数字、.-_,长度1-16', imageName: '支持英文、中文、数字、:.-_,长度1-30', volumeName: '支持英文、数字、.-和_,长度1-30', - complexityPassword: '请输入 8 位以上、必须含有字母、数字、特殊符号的密码', + complexityPassword: '请输入 8 位以上、必须含有字母、数字、特殊符号(~!@#$%^&*.-_)的密码', commonPassword: '请输入 6 位以上长度密码', linuxName: '长度1-30,名称不能含有{0}等符号', email: '请输入正确的邮箱', @@ -615,9 +615,10 @@ export default { saveAndConn: '保存并连接', connTestOk: '连接信息可用', connTestFailed: '连接不可用,请检查连接信息!', - hostList: '主机信息', + host: '主机', createConn: '新建连接', - createGroup: '创建分组', + group: '分组', + groupChange: '切换分组', expand: '全部展开', fold: '全部收缩', batchInput: '批量输入', diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index 5413037c4..51bc9af57 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -3,7 +3,7 @@ - + diff --git a/frontend/src/views/host/terminal/host/change-group/index.vue b/frontend/src/views/host/terminal/host/change-group/index.vue new file mode 100644 index 000000000..a3344ddc3 --- /dev/null +++ b/frontend/src/views/host/terminal/host/change-group/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/views/host/terminal/host/group/index.vue b/frontend/src/views/host/terminal/host/group/index.vue new file mode 100644 index 000000000..abaf5223b --- /dev/null +++ b/frontend/src/views/host/terminal/host/group/index.vue @@ -0,0 +1,183 @@ + + diff --git a/frontend/src/views/host/terminal/host/index.vue b/frontend/src/views/host/terminal/host/index.vue index 7916205f5..9b599451f 100644 --- a/frontend/src/views/host/terminal/host/index.vue +++ b/frontend/src/views/host/terminal/host/index.vue @@ -1,239 +1,140 @@