mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-14 01:34:47 +08:00
fix: 主机改为列表展示,增加分组切换
This commit is contained in:
parent
abd51c5853
commit
f941cd9751
@ -95,7 +95,7 @@ func (b *BaseApi) TestByID(c *gin.Context) {
|
|||||||
// @Param request body dto.SearchForTree true "request"
|
// @Param request body dto.SearchForTree true "request"
|
||||||
// @Success 200 {anrry} dto.HostTree
|
// @Success 200 {anrry} dto.HostTree
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /hosts/search [post]
|
// @Router /hosts/tree [post]
|
||||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||||
var req dto.SearchForTree
|
var req dto.SearchForTree
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@ -112,6 +112,33 @@ func (b *BaseApi) HostTree(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, data)
|
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
|
// @Tags Host
|
||||||
// @Summary Load host info
|
// @Summary Load host info
|
||||||
// @Description 加载主机信息
|
// @Description 加载主机信息
|
||||||
@ -143,13 +170,13 @@ func (b *BaseApi) GetHostInfo(c *gin.Context) {
|
|||||||
// @Summary Delete host
|
// @Summary Delete host
|
||||||
// @Description 删除主机
|
// @Description 删除主机
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.OperateByID true "request"
|
// @Param request body dto.BatchDeleteReq true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /hosts/del [post]
|
// @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) {
|
func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||||
var req dto.OperateByID
|
var req dto.BatchDeleteReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
return
|
return
|
||||||
@ -159,7 +186,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
|
|||||||
return
|
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)
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -202,3 +229,32 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
helper.SuccessWithData(c, nil)
|
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)
|
||||||
|
}
|
||||||
|
@ -27,10 +27,21 @@ type HostConnTest struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchHostWithPage struct {
|
||||||
|
PageInfo
|
||||||
|
Group string `json:"group"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
type SearchForTree struct {
|
type SearchForTree struct {
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChangeHostGroup struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
Group string `json:"group" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type HostInfo struct {
|
type HostInfo struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
@ -11,10 +11,12 @@ type HostRepo struct{}
|
|||||||
type IHostRepo interface {
|
type IHostRepo interface {
|
||||||
Get(opts ...DBOption) (model.Host, error)
|
Get(opts ...DBOption) (model.Host, error)
|
||||||
GetList(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
|
WithByInfo(info string) DBOption
|
||||||
WithByPort(port uint) DBOption
|
WithByPort(port uint) DBOption
|
||||||
WithByUser(user string) DBOption
|
WithByUser(user string) DBOption
|
||||||
WithByAddr(addr string) DBOption
|
WithByAddr(addr string) DBOption
|
||||||
|
WithByGroup(group string) DBOption
|
||||||
Create(host *model.Host) error
|
Create(host *model.Host) error
|
||||||
ChangeGroup(oldGroup, newGroup string) error
|
ChangeGroup(oldGroup, newGroup string) error
|
||||||
Update(id uint, vars map[string]interface{}) 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
|
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 {
|
func (c *HostRepo) WithByInfo(info string) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
if len(info) == 0 {
|
if len(info) == 0 {
|
||||||
@ -70,6 +84,14 @@ func (u *HostRepo) WithByAddr(addr string) DBOption {
|
|||||||
return g.Where("addr = ?", addr)
|
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 {
|
func (u *HostRepo) Create(host *model.Host) error {
|
||||||
return global.DB.Create(host).Error
|
return global.DB.Create(host).Error
|
||||||
|
@ -17,9 +17,10 @@ type IHostService interface {
|
|||||||
TestLocalConn(id uint) bool
|
TestLocalConn(id uint) bool
|
||||||
GetHostInfo(id uint) (*model.Host, error)
|
GetHostInfo(id uint) (*model.Host, error)
|
||||||
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
|
||||||
|
SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error)
|
||||||
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
|
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
|
||||||
Update(id uint, upMap map[string]interface{}) error
|
Update(id uint, upMap map[string]interface{}) error
|
||||||
Delete(id uint) error
|
Delete(id []uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIHostService() IHostService {
|
func NewIHostService() IHostService {
|
||||||
@ -63,6 +64,22 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
|
|||||||
return &host, err
|
return &host, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error) {
|
||||||
|
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) {
|
func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) {
|
||||||
hosts, err := hostRepo.GetList(hostRepo.WithByInfo(search.Info))
|
hosts, err := hostRepo.GetList(hostRepo.WithByInfo(search.Info))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -136,15 +153,17 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
|
|||||||
return &hostinfo, nil
|
return &hostinfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HostService) Delete(id uint) error {
|
func (u *HostService) Delete(ids []uint) error {
|
||||||
host, _ := hostRepo.Get(commonRepo.WithByID(id))
|
hosts, _ := hostRepo.GetList(commonRepo.WithIdsIn(ids))
|
||||||
if host.ID == 0 {
|
for _, host := range hosts {
|
||||||
return constant.ErrRecordNotFound
|
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 hostRepo.Delete(commonRepo.WithIdsIn(ids))
|
||||||
return errors.New("the local connection information cannot be deleted!")
|
|
||||||
}
|
|
||||||
return hostRepo.Delete(commonRepo.WithByID(id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
|
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
|
||||||
|
@ -19,7 +19,9 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("", baseApi.CreateHost)
|
hostRouter.POST("", baseApi.CreateHost)
|
||||||
hostRouter.POST("/del", baseApi.DeleteHost)
|
hostRouter.POST("/del", baseApi.DeleteHost)
|
||||||
hostRouter.POST("/update", baseApi.UpdateHost)
|
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/byinfo", baseApi.TestByInfo)
|
||||||
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
|
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
|
||||||
hostRouter.GET(":id", baseApi.GetHostInfo)
|
hostRouter.GET(":id", baseApi.GetHostInfo)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommonModel } from '.';
|
import { CommonModel, ReqPage } from '.';
|
||||||
|
|
||||||
export namespace Host {
|
export namespace Host {
|
||||||
export interface HostTree {
|
export interface HostTree {
|
||||||
@ -40,7 +40,15 @@ export namespace Host {
|
|||||||
privateKey: string;
|
privateKey: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
export interface GroupChange {
|
||||||
|
id: number;
|
||||||
|
group: string;
|
||||||
|
}
|
||||||
export interface ReqSearch {
|
export interface ReqSearch {
|
||||||
info?: string;
|
info?: string;
|
||||||
}
|
}
|
||||||
|
export interface SearchWithPage extends ReqPage {
|
||||||
|
group: string;
|
||||||
|
info?: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,11 @@ import { Command } from '../interface/command';
|
|||||||
import { Group } from '../interface/group';
|
import { Group } from '../interface/group';
|
||||||
import { Host } from '../interface/host';
|
import { Host } from '../interface/host';
|
||||||
|
|
||||||
|
export const searchHosts = (params: Host.SearchWithPage) => {
|
||||||
|
return http.post<ResPage<Host.Host>>(`/hosts/search`, params);
|
||||||
|
};
|
||||||
export const getHostTree = (params: Host.ReqSearch) => {
|
export const getHostTree = (params: Host.ReqSearch) => {
|
||||||
return http.post<Array<Host.HostTree>>(`/hosts/search`, params);
|
return http.post<Array<Host.HostTree>>(`/hosts/tree`, params);
|
||||||
};
|
};
|
||||||
export const getHostInfo = (id: number) => {
|
export const getHostInfo = (id: number) => {
|
||||||
return http.get<Host.Host>(`/hosts/` + id);
|
return http.get<Host.Host>(`/hosts/` + id);
|
||||||
@ -22,8 +25,11 @@ export const testByID = (id: number) => {
|
|||||||
export const editHost = (params: Host.HostOperate) => {
|
export const editHost = (params: Host.HostOperate) => {
|
||||||
return http.post(`/hosts/update`, params);
|
return http.post(`/hosts/update`, params);
|
||||||
};
|
};
|
||||||
export const deleteHost = (id: number) => {
|
export const editHostGroup = (params: Host.GroupChange) => {
|
||||||
return http.post(`/hosts/del`, { id: id });
|
return http.post(`/hosts/update/group`, params);
|
||||||
|
};
|
||||||
|
export const deleteHost = (params: { ids: number[] }) => {
|
||||||
|
return http.post(`/hosts/del`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
// group
|
// group
|
||||||
|
@ -19,7 +19,7 @@ const complexityPassword = (rule: any, value: any, callback: any) => {
|
|||||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||||
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
|
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
|
||||||
} else {
|
} 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 !== '') {
|
if (!reg.test(value) && value !== '') {
|
||||||
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
|
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,7 +126,7 @@ export default {
|
|||||||
imageName: 'Support English, Chinese, numbers, :.-_, length 1-30',
|
imageName: 'Support English, Chinese, numbers, :.-_, length 1-30',
|
||||||
volumeName: 'Support English, numbers, .-_, length 1-30',
|
volumeName: 'Support English, numbers, .-_, length 1-30',
|
||||||
complexityPassword:
|
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',
|
commonPassword: 'Please enter a password with more than 6 characters',
|
||||||
email: 'Email format error',
|
email: 'Email format error',
|
||||||
number: 'Please enter the correct number',
|
number: 'Please enter the correct number',
|
||||||
@ -606,9 +606,10 @@ export default {
|
|||||||
saveAndConn: 'Save and Connect',
|
saveAndConn: 'Save and Connect',
|
||||||
connTestOk: 'Connection information available',
|
connTestOk: 'Connection information available',
|
||||||
connTestFailed: 'Connection unavailable, please check connection information!',
|
connTestFailed: 'Connection unavailable, please check connection information!',
|
||||||
hostList: 'Host information',
|
host: 'Host',
|
||||||
createConn: 'Create a connection',
|
createConn: 'Create a connection',
|
||||||
createGroup: 'Create a group',
|
group: 'Group',
|
||||||
|
groupChange: 'Change group',
|
||||||
expand: 'Expand all',
|
expand: 'Expand all',
|
||||||
fold: 'All contract',
|
fold: 'All contract',
|
||||||
batchInput: 'Batch input',
|
batchInput: 'Batch input',
|
||||||
|
@ -130,7 +130,7 @@ export default {
|
|||||||
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
||||||
imageName: '支持英文、中文、数字、:.-_,长度1-30',
|
imageName: '支持英文、中文、数字、:.-_,长度1-30',
|
||||||
volumeName: '支持英文、数字、.-和_,长度1-30',
|
volumeName: '支持英文、数字、.-和_,长度1-30',
|
||||||
complexityPassword: '请输入 8 位以上、必须含有字母、数字、特殊符号的密码',
|
complexityPassword: '请输入 8 位以上、必须含有字母、数字、特殊符号(~!@#$%^&*.-_)的密码',
|
||||||
commonPassword: '请输入 6 位以上长度密码',
|
commonPassword: '请输入 6 位以上长度密码',
|
||||||
linuxName: '长度1-30,名称不能含有{0}等符号',
|
linuxName: '长度1-30,名称不能含有{0}等符号',
|
||||||
email: '请输入正确的邮箱',
|
email: '请输入正确的邮箱',
|
||||||
@ -615,9 +615,10 @@ export default {
|
|||||||
saveAndConn: '保存并连接',
|
saveAndConn: '保存并连接',
|
||||||
connTestOk: '连接信息可用',
|
connTestOk: '连接信息可用',
|
||||||
connTestFailed: '连接不可用,请检查连接信息!',
|
connTestFailed: '连接不可用,请检查连接信息!',
|
||||||
hostList: '主机信息',
|
host: '主机',
|
||||||
createConn: '新建连接',
|
createConn: '新建连接',
|
||||||
createGroup: '创建分组',
|
group: '分组',
|
||||||
|
groupChange: '切换分组',
|
||||||
expand: '全部展开',
|
expand: '全部展开',
|
||||||
fold: '全部收缩',
|
fold: '全部收缩',
|
||||||
batchInput: '批量输入',
|
batchInput: '批量输入',
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<DrawerHeader :header="$t('cronjob.cronTask')" :back="handleClose" />
|
<DrawerHeader :header="$t('cronjob.cronTask')" :back="handleClose" />
|
||||||
</template>
|
</template>
|
||||||
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules" label-width="120px">
|
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||||
<el-row type="flex" justify="center">
|
<el-row type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item :label="$t('cronjob.taskType')" prop="type">
|
<el-form-item :label="$t('cronjob.taskType')" prop="type">
|
||||||
|
98
frontend/src/views/host/terminal/host/change-group/index.vue
Normal file
98
frontend/src/views/host/terminal/host/change-group/index.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('terminal.groupChange')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form ref="hostInfoRef" label-position="top" :model="dialogData" :rules="rules">
|
||||||
|
<el-form-item :label="$t('commons.table.group')" prop="group">
|
||||||
|
<el-select filterable v-model="dialogData.group" 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>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit(hostInfoRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import type { ElForm } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { editHostGroup, getGroupList } from '@/api/modules/host';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
interface DialogProps {
|
||||||
|
id: number;
|
||||||
|
group: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref(false);
|
||||||
|
const dialogData = ref<DialogProps>({
|
||||||
|
id: 0,
|
||||||
|
group: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupList = ref();
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
dialogData.value = params;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
loadGroups();
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const hostInfoRef = ref<FormInstance>();
|
||||||
|
const rules = reactive({
|
||||||
|
group: [Rules.requiredSelect],
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadGroups = async () => {
|
||||||
|
const res = await getGroupList({ type: 'host' });
|
||||||
|
groupList.value = res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
await editHostGroup(dialogData.value)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
183
frontend/src/views/host/terminal/host/group/index.vue
Normal file
183
frontend/src/views/host/terminal/host/group/index.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer :close-on-click-modal="false" v-model="drawerVisiable" size="50%" :before-close="handleClose">
|
||||||
|
<template #header>
|
||||||
|
<Header :header="$t('website.group')" :back="handleClose"></Header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ComplexTable v-loading="loading" :data="data" @search="search()">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-button type="primary" @click="openCreate">{{ $t('website.createGroup') }}</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column :label="$t('commons.table.name')" prop="name">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="!row.edit">
|
||||||
|
{{ row.name }}
|
||||||
|
</span>
|
||||||
|
<el-input v-if="row.edit" v-model="row.name"></el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('commons.table.operate')">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<div>
|
||||||
|
<el-button link v-if="row.edit" type="primary" @click="onSaveGroup(row)">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
v-if="!row.edit"
|
||||||
|
:disabled="row.name === 'default'"
|
||||||
|
type="primary"
|
||||||
|
@click="onEditGroup($index)"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.edit') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
v-if="!row.edit"
|
||||||
|
:disabled="row.name === 'default'"
|
||||||
|
type="primary"
|
||||||
|
@click="onDeleteGroup($index)"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button link v-if="row.edit" type="primary" @click="cancelEdit($index)">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</ComplexTable>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
|
import Header from '@/components/drawer-header/index.vue';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { addGroup, deleteGroup, editGroup, getGroupList } from '@/api/modules/host';
|
||||||
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
interface groupData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let drawerVisiable = ref(false);
|
||||||
|
let data = ref();
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
data.value = [];
|
||||||
|
emit('search');
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
data.value = [];
|
||||||
|
getGroupList({ type: 'host' }).then((res) => {
|
||||||
|
for (const d of res.data) {
|
||||||
|
const g = {
|
||||||
|
id: d.id,
|
||||||
|
name: d.name,
|
||||||
|
edit: false,
|
||||||
|
};
|
||||||
|
data.value.push(g);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveGroup = (create: groupData) => {
|
||||||
|
const group = {
|
||||||
|
id: create.id,
|
||||||
|
type: 'host',
|
||||||
|
name: create.name,
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
if (group.id == 0) {
|
||||||
|
addGroup(group)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
editGroup(group)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const acceptParams = async () => {
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const openCreate = () => {
|
||||||
|
for (const d of data.value) {
|
||||||
|
if (d.name == '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (d.edit) {
|
||||||
|
d.edit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const g = {
|
||||||
|
id: 0,
|
||||||
|
type: 'host',
|
||||||
|
name: '',
|
||||||
|
edit: true,
|
||||||
|
};
|
||||||
|
data.value.push(g);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteGroup = async (index: number) => {
|
||||||
|
const group = data.value[index];
|
||||||
|
if (group.id > 0) {
|
||||||
|
await useDeleteData(deleteGroup, group.id, 'terminal.groupDeleteHelper')
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditGroup = (index: number) => {
|
||||||
|
for (const i in data.value) {
|
||||||
|
const d = data.value[i];
|
||||||
|
if (d.name == '') {
|
||||||
|
data.value.splice(Number(i), 1);
|
||||||
|
}
|
||||||
|
if (d.edit) {
|
||||||
|
d.edit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.value[index].edit = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelEdit = (index: number) => {
|
||||||
|
if (data.value[index].id == 0) {
|
||||||
|
data.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
data.value[index].edit = false;
|
||||||
|
}
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ acceptParams });
|
||||||
|
</script>
|
@ -1,239 +1,140 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-row class="row-box" style="margin-top: 20px" :gutter="20">
|
<LayoutContent v-loading="loading" :title="$t('terminal.host')">
|
||||||
<el-col :span="8">
|
<template #toolbar>
|
||||||
<el-card class="el-card">
|
<el-row>
|
||||||
<el-tooltip
|
<el-col :span="20">
|
||||||
class="box-item"
|
<el-button type="primary" @click="onOpenDialog('create')">
|
||||||
effect="dark"
|
{{ $t('terminal.addHost') }}
|
||||||
:content="$t('terminal.createConn')"
|
</el-button>
|
||||||
placement="top-start"
|
<el-button type="primary" plain @click="onOpenGroupDialog()">
|
||||||
>
|
{{ $t('terminal.group') }}
|
||||||
<el-button icon="Plus" @click="restHostForm" />
|
</el-button>
|
||||||
</el-tooltip>
|
<el-button type="primary" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
||||||
<el-tooltip
|
{{ $t('commons.button.delete') }}
|
||||||
class="box-item"
|
</el-button>
|
||||||
effect="dark"
|
</el-col>
|
||||||
:content="$t('terminal.createGroup')"
|
<el-col :span="4">
|
||||||
placement="top-start"
|
<div class="search-button">
|
||||||
>
|
<el-input
|
||||||
<el-button icon="FolderAdd" @click="onGroupCreate" />
|
v-model="info"
|
||||||
</el-tooltip>
|
clearable
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.expand')" placement="top-start">
|
@clear="search()"
|
||||||
<el-button icon="Expand" @click="setTreeStatus(true)" />
|
suffix-icon="Search"
|
||||||
</el-tooltip>
|
@keyup.enter="search()"
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.fold')" placement="top-start">
|
@blur="search()"
|
||||||
<el-button icon="Fold" @click="setTreeStatus(false)" />
|
:placeholder="$t('commons.button.search')"
|
||||||
</el-tooltip>
|
></el-input>
|
||||||
<el-input @input="loadHostTree" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
</div>
|
||||||
<template #append><el-button icon="search" @click="loadHostTree" /></template>
|
</el-col>
|
||||||
</el-input>
|
</el-row>
|
||||||
<el-input v-if="groupInputShow" clearable style="margin-top: 5px" v-model="groupInputValue">
|
</template>
|
||||||
<template #append>
|
<template #search>
|
||||||
<el-button-group>
|
<el-select v-model="group" @change="search()" clearable>
|
||||||
<el-button icon="Check" @click="onCreateGroup(groupInputValue)" />
|
<template #prefix>{{ $t('terminal.group') }}</template>
|
||||||
<el-button icon="Close" @click="groupInputShow = false" />
|
<el-option v-for="item in groupList" :key="item.name" :value="item.name" :label="item.name" />
|
||||||
</el-button-group>
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<ComplexTable
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
v-model:selects="selects"
|
||||||
|
:data="data"
|
||||||
|
@search="search"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" :selectable="selectable" fix />
|
||||||
|
<el-table-column :label="$t('commons.table.name')" prop="name" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.addr === '127.0.0.1'">{{ $t('terminal.localhost') }}</span>
|
||||||
|
<span v-else>{{ row.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-table-column>
|
||||||
<el-tree
|
<el-table-column :label="$t('commons.table.group')" show-overflow-tooltip prop="groupBelong" />
|
||||||
ref="tree"
|
<el-table-column :label="$t('terminal.ip')">
|
||||||
:expand-on-click-node="false"
|
<template #default="{ row }">{{ row.addr }}:{{ row.port }}</template>
|
||||||
node-key="id"
|
</el-table-column>
|
||||||
:default-expand-all="true"
|
<el-table-column :label="$t('terminal.user')" show-overflow-tooltip prop="user" />
|
||||||
:data="hostTree"
|
<el-table-column
|
||||||
:props="defaultProps"
|
:label="$t('commons.table.description')"
|
||||||
>
|
show-overflow-tooltip
|
||||||
<template #default="{ node, data }">
|
prop="description"
|
||||||
<span class="custom-tree-node" @mouseover="hover = data.id" @mouseleave="hover = null">
|
/>
|
||||||
<div v-if="node.label !== currentGroup || !data.onEdit">
|
<fu-table-operations width="200px" :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||||
<span v-if="node.label.length <= 35" @click="onEdit(node, data)">
|
</ComplexTable>
|
||||||
{{ node.label }}
|
</template>
|
||||||
</span>
|
</LayoutContent>
|
||||||
<el-tooltip v-else :content="node.label" placement="top-start">
|
|
||||||
<span>{{ node.label.substring(0, 32) }}...</span>
|
<OperateDialog @search="search" ref="dialogRef" />
|
||||||
</el-tooltip>
|
<GroupDialog @search="search" ref="dialogGroupRef" />
|
||||||
</div>
|
<GroupChangeDialog @search="search" ref="dialogGroupChangeRef" />
|
||||||
<el-input v-else v-model="currentGroupValue"></el-input>
|
|
||||||
<div
|
|
||||||
style="margin-left: 10px"
|
|
||||||
v-if="!(node.level === 1 && data.label === 'default') && data.id === hover"
|
|
||||||
>
|
|
||||||
<el-button v-if="!data.onEdit" icon="Edit" link @click="onEdit(node, data)" />
|
|
||||||
<el-button
|
|
||||||
v-if="!data.onEdit && node.label.indexOf('@127.0.0.1:') === -1"
|
|
||||||
icon="Delete"
|
|
||||||
link
|
|
||||||
@click="onDelete(node, data)"
|
|
||||||
/>
|
|
||||||
<el-button v-if="data.onEdit" icon="Check" link @click="onUpdateGroup()" />
|
|
||||||
<el-button v-if="data.onEdit" icon="Close" link @click="data.onEdit = false" />
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-tree>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-card class="el-card">
|
|
||||||
<el-form ref="hostInfoRef" label-width="100px" :model="hostInfo" :rules="rules">
|
|
||||||
<el-form-item :label="$t('terminal.ip')" prop="addr">
|
|
||||||
<span v-if="hostInfo.addr === '127.0.0.1' && hostOperation === 'edit'">
|
|
||||||
{{ hostInfo.addr }}
|
|
||||||
</span>
|
|
||||||
<el-input v-else clearable v-model="hostInfo.addr" />
|
|
||||||
</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 :label="$t('terminal.port')" prop="port">
|
|
||||||
<el-input clearable v-model.number="hostInfo.port" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('commons.table.group')" prop="groupBelong">
|
|
||||||
<el-select filterable 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="$t('commons.table.title')" prop="name">
|
|
||||||
<el-input clearable v-model="hostInfo.name" />
|
|
||||||
</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 @click="restHostForm">
|
|
||||||
{{ $t('commons.button.reset') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="submitAddHost(hostInfoRef, 'testconn')">
|
|
||||||
{{ $t('terminal.testConn') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="hostOperation === 'create'"
|
|
||||||
type="primary"
|
|
||||||
@click="submitAddHost(hostInfoRef, 'create')"
|
|
||||||
>
|
|
||||||
{{ $t('commons.button.save') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="hostOperation === 'edit'"
|
|
||||||
type="primary"
|
|
||||||
@click="submitAddHost(hostInfoRef, 'edit')"
|
|
||||||
>
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from 'vue';
|
import LayoutContent from '@/layout/layout-content.vue';
|
||||||
import type { ElForm } from 'element-plus';
|
import GroupDialog from '@/views/host/terminal/host/group/index.vue';
|
||||||
import { Rules } from '@/global/form-rules';
|
import GroupChangeDialog from '@/views/host/terminal/host/change-group/index.vue';
|
||||||
import { Host } from '@/api/interface/host';
|
import OperateDialog from '@/views/host/terminal/host/operate/index.vue';
|
||||||
import { Group } from '@/api/interface/group';
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
import {
|
import { deleteHost, getGroupList, searchHosts } from '@/api/modules/host';
|
||||||
getHostTree,
|
import { reactive, ref } from 'vue';
|
||||||
getHostInfo,
|
|
||||||
addHost,
|
|
||||||
editHost,
|
|
||||||
deleteHost,
|
|
||||||
testByInfo,
|
|
||||||
getGroupList,
|
|
||||||
addGroup,
|
|
||||||
editGroup,
|
|
||||||
deleteGroup,
|
|
||||||
} from '@/api/modules/host';
|
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import type Node from 'element-plus/es/components/tree/src/model/node';
|
import { Host } from '@/api/interface/host';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
const loading = ref();
|
||||||
const hostInfoRef = ref<FormInstance>();
|
const data = ref();
|
||||||
const rules = reactive({
|
const groupList = ref();
|
||||||
groupBelong: [Rules.requiredSelect],
|
const selects = ref<any>([]);
|
||||||
addr: [Rules.requiredInput],
|
const paginationConfig = reactive({
|
||||||
port: [Rules.requiredInput, Rules.port],
|
currentPage: 1,
|
||||||
user: [Rules.requiredInput],
|
pageSize: 10,
|
||||||
authMode: [Rules.requiredSelect],
|
total: 0,
|
||||||
password: [Rules.requiredInput],
|
|
||||||
privateKey: [Rules.requiredInput],
|
|
||||||
});
|
});
|
||||||
let hostOperation = ref<string>('create');
|
const info = ref();
|
||||||
let hostInfo = reactive<Host.HostOperate>({
|
const group = ref();
|
||||||
id: 0,
|
const dialogGroupChangeRef = ref();
|
||||||
name: '',
|
|
||||||
groupBelong: 'default',
|
|
||||||
addr: '',
|
|
||||||
port: 22,
|
|
||||||
user: '',
|
|
||||||
authMode: 'password',
|
|
||||||
password: '',
|
|
||||||
privateKey: '',
|
|
||||||
description: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Tree {
|
|
||||||
id: number;
|
|
||||||
label: string;
|
|
||||||
children?: Tree[];
|
|
||||||
onEdit: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let searcConfig = reactive<Host.ReqSearch>({
|
|
||||||
info: '',
|
|
||||||
});
|
|
||||||
const tree = ref<any>(null);
|
|
||||||
const hover = ref();
|
|
||||||
const hostTree = ref<Array<Host.HostTree>>();
|
|
||||||
const defaultProps = {
|
|
||||||
label: 'label',
|
|
||||||
children: 'children',
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupList = ref<Array<Group.GroupInfo>>();
|
|
||||||
|
|
||||||
const groupInputValue = ref();
|
|
||||||
const currentGroup = ref();
|
|
||||||
const currentGroupID = ref();
|
|
||||||
const currentGroupValue = ref();
|
|
||||||
|
|
||||||
let groupOperation = ref<string>('create');
|
|
||||||
let groupInputShow = ref<boolean>(false);
|
|
||||||
|
|
||||||
const loadHostTree = async () => {
|
|
||||||
const res = await getHostTree(searcConfig);
|
|
||||||
hostTree.value = res.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const acceptParams = () => {
|
const acceptParams = () => {
|
||||||
loadHostTree();
|
search();
|
||||||
loadGroups();
|
};
|
||||||
|
|
||||||
|
function selectable(row) {
|
||||||
|
return row.addr !== '127.0.0.1';
|
||||||
|
}
|
||||||
|
const dialogRef = ref();
|
||||||
|
const onOpenDialog = async (
|
||||||
|
title: string,
|
||||||
|
rowData: Partial<Host.Host> = {
|
||||||
|
groupBelong: 'default',
|
||||||
|
port: 22,
|
||||||
|
user: 'root',
|
||||||
|
authMode: 'password',
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let params = {
|
||||||
|
title,
|
||||||
|
rowData: { ...rowData },
|
||||||
|
};
|
||||||
|
dialogRef.value!.acceptParams(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogGroupRef = ref();
|
||||||
|
const onOpenGroupDialog = () => {
|
||||||
|
dialogGroupRef.value!.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBatchDelete = async (row: Host.Host | null) => {
|
||||||
|
let ids: Array<number> = [];
|
||||||
|
if (row) {
|
||||||
|
ids.push(row.id);
|
||||||
|
} else {
|
||||||
|
selects.value.forEach((item: Host.Host) => {
|
||||||
|
ids.push(item.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await useDeleteData(deleteHost, { ids: ids }, 'commons.msg.delete');
|
||||||
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadGroups = async () => {
|
const loadGroups = async () => {
|
||||||
@ -241,129 +142,54 @@ const loadGroups = async () => {
|
|||||||
groupList.value = res.data;
|
groupList.value = res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
function setTreeStatus(expend: boolean) {
|
const buttons = [
|
||||||
for (let i = 0; i < tree.value.store._getAllNodes().length; i++) {
|
{
|
||||||
tree.value.store._getAllNodes()[i].expanded = expend;
|
label: i18n.global.t('terminal.groupChange'),
|
||||||
}
|
click: (row: any) => {
|
||||||
}
|
dialogGroupChangeRef.value!.acceptParams({ id: row.id, group: row.groupBelong });
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return row.addr === '127.0.0.1';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.edit'),
|
||||||
|
click: (row: any) => {
|
||||||
|
onOpenDialog('edit', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.delete'),
|
||||||
|
click: (row: Host.Host) => {
|
||||||
|
onBatchDelete(row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return row.addr === '127.0.0.1';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function restHostForm() {
|
const search = async () => {
|
||||||
if (hostInfoRef.value) {
|
let params = {
|
||||||
hostInfoRef.value.resetFields();
|
page: paginationConfig.currentPage,
|
||||||
}
|
pageSize: paginationConfig.pageSize,
|
||||||
}
|
group: group.value,
|
||||||
|
info: info.value,
|
||||||
const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
};
|
||||||
if (!formEl) return;
|
|
||||||
formEl.validate(async (valid) => {
|
|
||||||
if (!valid) return;
|
|
||||||
switch (ops) {
|
|
||||||
case 'create':
|
|
||||||
await addHost(hostInfo);
|
|
||||||
restHostForm();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
loadHostTree();
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
await editHost(hostInfo);
|
|
||||||
restHostForm();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
loadHostTree();
|
|
||||||
break;
|
|
||||||
case 'testconn':
|
|
||||||
await testByInfo(hostInfo).then((res) => {
|
|
||||||
if (res.data) {
|
|
||||||
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
|
||||||
} else {
|
|
||||||
MsgSuccess(i18n.global.t('terminal.connTestFailed'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onGroupCreate = () => {
|
|
||||||
groupInputShow.value = true;
|
|
||||||
groupInputValue.value = '';
|
|
||||||
groupOperation.value = 'create';
|
|
||||||
};
|
|
||||||
const onCreateGroup = async (name: string) => {
|
|
||||||
if (groupOperation.value === 'create') {
|
|
||||||
let group = { id: 0, name: name, type: 'host' };
|
|
||||||
await addGroup(group);
|
|
||||||
groupOperation.value = '';
|
|
||||||
groupInputShow.value = false;
|
|
||||||
}
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
groupOperation.value = '';
|
|
||||||
groupInputShow.value = false;
|
|
||||||
loadHostTree();
|
|
||||||
loadGroups();
|
loadGroups();
|
||||||
};
|
loading.value = true;
|
||||||
|
await searchHosts(params)
|
||||||
const onUpdateGroup = async () => {
|
.then((res) => {
|
||||||
if (currentGroup.value === currentGroupValue.value) {
|
loading.value = false;
|
||||||
currentGroup.value = '';
|
data.value = res.data.items || [];
|
||||||
return;
|
paginationConfig.total = res.data.total;
|
||||||
}
|
})
|
||||||
let group = { id: currentGroupID.value, name: currentGroupValue.value, type: 'host' };
|
.catch(() => {
|
||||||
await editGroup(group);
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
});
|
||||||
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, 'terminal.groupDeleteHelper');
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (node.level === 1) {
|
|
||||||
currentGroup.value = data.label;
|
|
||||||
currentGroupValue.value = data.label;
|
|
||||||
currentGroupID.value = data.id - 10000;
|
|
||||||
groupOperation.value = 'edit';
|
|
||||||
data.onEdit = true;
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.custom-tree-node {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 14px;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
177
frontend/src/views/host/terminal/host/operate/index.vue
Normal file
177
frontend/src/views/host/terminal/host/operate/index.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('terminal.host')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form ref="hostInfoRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||||
|
<el-form-item :label="$t('terminal.ip')" prop="addr">
|
||||||
|
<span v-if="dialogData.rowData!.addr === '127.0.0.1' && dialogData.title === 'edit'">
|
||||||
|
{{ dialogData.rowData!.addr }}
|
||||||
|
</span>
|
||||||
|
<el-input v-else clearable v-model="dialogData.rowData!.addr" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.user')" prop="user">
|
||||||
|
<el-input clearable v-model="dialogData.rowData!.user" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
||||||
|
<el-radio-group v-model="dialogData.rowData!.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="dialogData.rowData!.authMode === 'password'"
|
||||||
|
prop="password"
|
||||||
|
>
|
||||||
|
<el-input clearable show-password type="password" v-model="dialogData.rowData!.password" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('terminal.key')"
|
||||||
|
v-if="dialogData.rowData!.authMode === 'key'"
|
||||||
|
prop="privateKey"
|
||||||
|
>
|
||||||
|
<el-input clearable type="textarea" v-model="dialogData.rowData!.privateKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.port')" prop="port">
|
||||||
|
<el-input clearable v-model.number="dialogData.rowData!.port" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.group')" prop="groupBelong">
|
||||||
|
<el-select
|
||||||
|
filterable
|
||||||
|
v-model="dialogData.rowData!.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="$t('commons.table.title')" prop="name">
|
||||||
|
<el-input clearable v-model="dialogData.rowData!.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||||
|
<el-input clearable type="textarea" v-model="dialogData.rowData!.description" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button @click="submitAddHost(hostInfoRef, 'testconn')">
|
||||||
|
{{ $t('terminal.testConn') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitAddHost(hostInfoRef, dialogData.title)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import type { ElForm } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { addHost, editHost, testByInfo, getGroupList } from '@/api/modules/host';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: any;
|
||||||
|
getTableList?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisiable = ref(false);
|
||||||
|
const dialogData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const groupList = ref();
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
dialogData.value = params;
|
||||||
|
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
loadGroups();
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const hostInfoRef = ref<FormInstance>();
|
||||||
|
const rules = reactive({
|
||||||
|
groupBelong: [Rules.requiredSelect],
|
||||||
|
addr: [Rules.requiredInput],
|
||||||
|
port: [Rules.requiredInput, Rules.port],
|
||||||
|
user: [Rules.requiredInput],
|
||||||
|
authMode: [Rules.requiredSelect],
|
||||||
|
password: [Rules.requiredInput],
|
||||||
|
privateKey: [Rules.requiredInput],
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadGroups = async () => {
|
||||||
|
const res = await getGroupList({ type: 'host' });
|
||||||
|
groupList.value = res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (ops === 'create') {
|
||||||
|
loading.value = true;
|
||||||
|
await addHost(dialogData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ops === 'edit') {
|
||||||
|
loading.value = true;
|
||||||
|
await editHost(dialogData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ops === 'testconn') {
|
||||||
|
loading.value = true;
|
||||||
|
await testByInfo(dialogData.value.rowData).then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
if (res.data) {
|
||||||
|
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
||||||
|
} else {
|
||||||
|
MsgSuccess(i18n.global.t('terminal.connTestFailed'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -60,7 +60,14 @@
|
|||||||
icon="Plus"
|
icon="Plus"
|
||||||
></el-button>
|
></el-button>
|
||||||
<el-popover ref="popoverRef" width="250px" trigger="hover" virtual-triggering persistent>
|
<el-popover ref="popoverRef" width="250px" trigger="hover" virtual-triggering persistent>
|
||||||
<el-button link type="primary" @click="onNewSsh">{{ $t('terminal.createConn') }}</el-button>
|
<div style="margin-left: 10px">
|
||||||
|
<el-button link type="primary" @click="onNewSsh">{{ $t('terminal.createConn') }}</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="margin-left: 10px">
|
||||||
|
<el-button link type="primary" @click="onNewLocal">
|
||||||
|
{{ $t('terminal.localhost') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
<div class="search-button" style="float: none">
|
<div class="search-button" style="float: none">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="hostfilterInfo"
|
v-model="hostfilterInfo"
|
||||||
|
@ -191,7 +191,7 @@ const registerForm = reactive({
|
|||||||
const registerRules = reactive({
|
const registerRules = reactive({
|
||||||
name: [Rules.requiredInput, Rules.userName],
|
name: [Rules.requiredInput, Rules.userName],
|
||||||
password: [Rules.requiredInput, Rules.password],
|
password: [Rules.requiredInput, Rules.password],
|
||||||
rePassword: [Rules.requiredInput, Rules.password, { validator: checkPassword, trigger: 'blur' }],
|
rePassword: [Rules.requiredInput, { validator: checkPassword, trigger: 'blur' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginButtonFocused = ref();
|
const loginButtonFocused = ref();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user