fix: 快速命令增加分组 (#2732)

Refs #2725
pull/2734/head
ssongliu 2023-10-31 14:14:30 +08:00 committed by GitHub
parent 45d233bade
commit fe1290e933
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 451 additions and 75 deletions

View File

@ -38,7 +38,7 @@ func (b *BaseApi) CreateCommand(c *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/command/search [post] // @Router /hosts/command/search [post]
func (b *BaseApi) SearchCommand(c *gin.Context) { func (b *BaseApi) SearchCommand(c *gin.Context) {
var req dto.SearchWithPage var req dto.SearchCommandWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }
@ -55,6 +55,23 @@ func (b *BaseApi) SearchCommand(c *gin.Context) {
}) })
} }
// @Tags Command
// @Summary Tree commands
// @Description 获取快速命令树
// @Accept json
// @Success 200 {Array} dto.CommandTree
// @Security ApiKeyAuth
// @Router /hosts/command/tree [get]
func (b *BaseApi) SearchCommandTree(c *gin.Context) {
list, err := commandService.SearchForTree()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Command // @Tags Command
// @Summary List commands // @Summary List commands
// @Description 获取快速命令列表 // @Description 获取快速命令列表
@ -110,6 +127,7 @@ func (b *BaseApi) UpdateCommand(c *gin.Context) {
upMap := make(map[string]interface{}) upMap := make(map[string]interface{})
upMap["name"] = req.Name upMap["name"] = req.Name
upMap["group_id"] = req.GroupID
upMap["command"] = req.Command upMap["command"] = req.Command
if err := commandService.Update(req.ID, upMap); err != nil { if err := commandService.Update(req.ID, upMap); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)

View File

@ -1,13 +1,29 @@
package dto package dto
type SearchCommandWithPage struct {
SearchWithPage
GroupID uint `json:"groupID"`
Info string `json:"info"`
}
type CommandOperate struct { type CommandOperate struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name" validate:"required"` GroupID uint `json:"groupID"`
Command string `json:"command" validate:"required"` GroupBelong string `json:"groupBelong"`
Name string `json:"name" validate:"required"`
Command string `json:"command" validate:"required"`
} }
type CommandInfo struct { type CommandInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` GroupID uint `json:"groupID"`
Command string `json:"command"` Name string `json:"name"`
Command string `json:"command"`
GroupBelong string `json:"groupBelong"`
}
type CommandTree struct {
ID uint `json:"id"`
Label string `json:"label"`
Children []CommandInfo `json:"children"`
} }

View File

@ -3,5 +3,6 @@ package model
type Command struct { type Command struct {
BaseModel BaseModel
Name string `gorm:"type:varchar(64);unique;not null" json:"name"` Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
GroupID uint `gorm:"type:decimal" json:"groupID"`
Command string `gorm:"type:varchar(256);not null" json:"command"` Command string `gorm:"type:varchar(256);not null" json:"command"`
} }

View File

@ -11,7 +11,8 @@ type CommandService struct{}
type ICommandService interface { type ICommandService interface {
List() ([]dto.CommandInfo, error) List() ([]dto.CommandInfo, error)
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) SearchForTree() ([]dto.CommandTree, error)
SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error)
Create(commandDto dto.CommandOperate) error Create(commandDto dto.CommandOperate) error
Update(id uint, upMap map[string]interface{}) error Update(id uint, upMap map[string]interface{}) error
Delete(ids []uint) error Delete(ids []uint) error
@ -37,14 +38,50 @@ func (u *CommandService) List() ([]dto.CommandInfo, error) {
return dtoCommands, err return dtoCommands, err
} }
func (u *CommandService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) { func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info)) cmdList, err := commandRepo.GetList()
if err != nil {
return nil, err
}
groups, err := groupRepo.GetList(commonRepo.WithByType("command"))
if err != nil {
return nil, err
}
var lists []dto.CommandTree
for _, group := range groups {
var data dto.CommandTree
data.ID = group.ID + 10000
data.Label = group.Name
for _, cmd := range cmdList {
if cmd.GroupID == group.ID {
data.Children = append(data.Children, dto.CommandInfo{ID: cmd.ID, Name: cmd.Name, Command: cmd.Command})
}
}
if len(data.Children) != 0 {
lists = append(lists, data)
}
}
return lists, err
}
func (u *CommandService) SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) {
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID))
if err != nil {
return 0, nil, err
}
groups, _ := groupRepo.GetList(commonRepo.WithByType("command"))
var dtoCommands []dto.CommandInfo var dtoCommands []dto.CommandInfo
for _, command := range commands { for _, command := range commands {
var item dto.CommandInfo var item dto.CommandInfo
if err := copier.Copy(&item, &command); err != nil { if err := copier.Copy(&item, &command); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
for _, group := range groups {
if command.GroupID == group.ID {
item.GroupBelong = group.Name
item.GroupID = group.ID
}
}
dtoCommands = append(dtoCommands, item) dtoCommands = append(dtoCommands, item)
} }
return total, dtoCommands, err return total, dtoCommands, err

View File

@ -62,6 +62,11 @@ func (u *GroupService) Delete(id uint) error {
if len(websites) > 0 { if len(websites) > 0 {
return buserr.New(constant.ErrGroupIsUsed) return buserr.New(constant.ErrGroupIsUsed)
} }
case "command":
commands, _ := commandRepo.GetList(commonRepo.WithByGroupID(id))
if len(commands) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
}
case "host": case "host":
hosts, _ := hostRepo.GetList(commonRepo.WithByGroupID(id)) hosts, _ := hostRepo.GetList(commonRepo.WithByGroupID(id))
if len(hosts) > 0 { if len(hosts) > 0 {

View File

@ -50,6 +50,7 @@ func Init() {
migrations.AddFavorite, migrations.AddFavorite,
migrations.AddBindAddress, migrations.AddBindAddress,
migrations.AddCommandGroup,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -28,3 +28,20 @@ var AddBindAddress = &gormigrate.Migration{
return nil return nil
}, },
} }
var AddCommandGroup = &gormigrate.Migration{
ID: "20231030-add-command-group",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Command{}); err != nil {
return err
}
defaultCommand := &model.Group{IsDefault: true, Name: "默认", Type: "command"}
if err := tx.Create(defaultCommand).Error; err != nil {
return err
}
if err := tx.Model(&model.Command{}).Where("1 = 1").Update("group_id", defaultCommand.ID).Error; err != nil {
return err
}
return nil
},
}

View File

@ -49,6 +49,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/command", baseApi.CreateCommand) hostRouter.POST("/command", baseApi.CreateCommand)
hostRouter.POST("/command/del", baseApi.DeleteCommand) hostRouter.POST("/command/del", baseApi.DeleteCommand)
hostRouter.POST("/command/search", baseApi.SearchCommand) hostRouter.POST("/command/search", baseApi.SearchCommand)
hostRouter.GET("/command/tree", baseApi.SearchCommandTree)
hostRouter.POST("/command/update", baseApi.UpdateCommand) hostRouter.POST("/command/update", baseApi.UpdateCommand)
hostRouter.POST("/tool", baseApi.GetToolStatus) hostRouter.POST("/tool", baseApi.GetToolStatus)

View File

@ -5261,7 +5261,7 @@ const docTemplate = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/request.FileOption" "$ref": "#/definitions/request.FileContentReq"
} }
} }
], ],
@ -7076,6 +7076,31 @@ const docTemplate = `{
} }
} }
}, },
"/hosts/command/tree": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取快速命令树",
"consumes": [
"application/json"
],
"tags": [
"Command"
],
"summary": "Tree commands",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "Array"
}
}
}
}
},
"/hosts/command/update": { "/hosts/command/update": {
"post": { "post": {
"security": [ "security": [
@ -12733,6 +12758,12 @@ const docTemplate = `{
"command": { "command": {
"type": "string" "type": "string"
}, },
"groupBelong": {
"type": "string"
},
"groupID": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -12751,6 +12782,12 @@ const docTemplate = `{
"command": { "command": {
"type": "string" "type": "string"
}, },
"groupBelong": {
"type": "string"
},
"groupID": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -14866,7 +14903,6 @@ const docTemplate = `{
"dto.OperationWithNameAndType": { "dto.OperationWithNameAndType": {
"type": "object", "type": "object",
"required": [ "required": [
"name",
"type" "type"
], ],
"properties": { "properties": {
@ -15052,7 +15088,6 @@ const docTemplate = `{
"dto.RecordSearch": { "dto.RecordSearch": {
"type": "object", "type": "object",
"required": [ "required": [
"name",
"page", "page",
"pageSize", "pageSize",
"type" "type"
@ -16463,6 +16498,9 @@ const docTemplate = `{
"pageSize" "pageSize"
], ],
"properties": { "properties": {
"all": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -16652,6 +16690,17 @@ const docTemplate = `{
} }
} }
}, },
"request.FileContentReq": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FileCreate": { "request.FileCreate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -17822,9 +17871,6 @@ const docTemplate = `{
}, },
"request.WebsiteInstallCheckReq": { "request.WebsiteInstallCheckReq": {
"type": "object", "type": "object",
"required": [
"InstallIds"
],
"properties": { "properties": {
"InstallIds": { "InstallIds": {
"type": "array", "type": "array",

View File

@ -5254,7 +5254,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/request.FileOption" "$ref": "#/definitions/request.FileContentReq"
} }
} }
], ],
@ -7069,6 +7069,31 @@
} }
} }
}, },
"/hosts/command/tree": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取快速命令树",
"consumes": [
"application/json"
],
"tags": [
"Command"
],
"summary": "Tree commands",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "Array"
}
}
}
}
},
"/hosts/command/update": { "/hosts/command/update": {
"post": { "post": {
"security": [ "security": [
@ -12726,6 +12751,12 @@
"command": { "command": {
"type": "string" "type": "string"
}, },
"groupBelong": {
"type": "string"
},
"groupID": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -12744,6 +12775,12 @@
"command": { "command": {
"type": "string" "type": "string"
}, },
"groupBelong": {
"type": "string"
},
"groupID": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -14859,7 +14896,6 @@
"dto.OperationWithNameAndType": { "dto.OperationWithNameAndType": {
"type": "object", "type": "object",
"required": [ "required": [
"name",
"type" "type"
], ],
"properties": { "properties": {
@ -15045,7 +15081,6 @@
"dto.RecordSearch": { "dto.RecordSearch": {
"type": "object", "type": "object",
"required": [ "required": [
"name",
"page", "page",
"pageSize", "pageSize",
"type" "type"
@ -16456,6 +16491,9 @@
"pageSize" "pageSize"
], ],
"properties": { "properties": {
"all": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -16645,6 +16683,17 @@
} }
} }
}, },
"request.FileContentReq": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FileCreate": { "request.FileCreate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -17815,9 +17864,6 @@
}, },
"request.WebsiteInstallCheckReq": { "request.WebsiteInstallCheckReq": {
"type": "object", "type": "object",
"required": [
"InstallIds"
],
"properties": { "properties": {
"InstallIds": { "InstallIds": {
"type": "array", "type": "array",

View File

@ -206,6 +206,10 @@ definitions:
properties: properties:
command: command:
type: string type: string
groupBelong:
type: string
groupID:
type: integer
id: id:
type: integer type: integer
name: name:
@ -215,6 +219,10 @@ definitions:
properties: properties:
command: command:
type: string type: string
groupBelong:
type: string
groupID:
type: integer
id: id:
type: integer type: integer
name: name:
@ -1656,7 +1664,6 @@ definitions:
type: type:
type: string type: string
required: required:
- name
- type - type
type: object type: object
dto.Options: dto.Options:
@ -1790,7 +1797,6 @@ definitions:
type: type:
type: string type: string
required: required:
- name
- page - page
- pageSize - pageSize
- type - type
@ -2712,6 +2718,8 @@ definitions:
type: object type: object
request.AppInstalledSearch: request.AppInstalledSearch:
properties: properties:
all:
type: boolean
name: name:
type: string type: string
page: page:
@ -2840,6 +2848,13 @@ definitions:
- name - name
- type - type
type: object type: object
request.FileContentReq:
properties:
path:
type: string
required:
- path
type: object
request.FileCreate: request.FileCreate:
properties: properties:
content: content:
@ -3632,8 +3647,6 @@ definitions:
items: items:
type: integer type: integer
type: array type: array
required:
- InstallIds
type: object type: object
request.WebsiteLogReq: request.WebsiteLogReq:
properties: properties:
@ -7643,7 +7656,7 @@ paths:
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/request.FileOption' $ref: '#/definitions/request.FileContentReq'
responses: responses:
"200": "200":
description: OK description: OK
@ -8803,6 +8816,21 @@ paths:
summary: Page commands summary: Page commands
tags: tags:
- Command - Command
/hosts/command/tree:
get:
consumes:
- application/json
description: 获取快速命令树
responses:
"200":
description: OK
schema:
type: Array
security:
- ApiKeyAuth: []
summary: Tree commands
tags:
- Command
/hosts/command/update: /hosts/command/update:
post: post:
consumes: consumes:

View File

@ -4,11 +4,13 @@ export namespace Command {
export interface CommandInfo { export interface CommandInfo {
id: number; id: number;
name: string; name: string;
groupID: number;
command: string; command: string;
} }
export interface CommandOperate { export interface CommandOperate {
id: number; id: number;
name: string; name: string;
groupID: number;
command: string; command: string;
} }
export interface CommandSearch extends ReqPage { export interface CommandSearch extends ReqPage {

View File

@ -1,23 +0,0 @@
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) => {
return http.post(`/commands/update`, params);
};
export const deleteCommand = (params: { ids: number[] }) => {
return http.post(`/commands/del`, params);
};

View File

@ -59,6 +59,9 @@ export const getCommandList = () => {
export const getCommandPage = (params: Command.CommandSearch) => { export const getCommandPage = (params: Command.CommandSearch) => {
return http.post<ResPage<Command.CommandInfo>>(`/hosts/command/search`, params); return http.post<ResPage<Command.CommandInfo>>(`/hosts/command/search`, params);
}; };
export const getCommandTree = () => {
return http.get<any>(`/hosts/command/tree`);
};
export const addCommand = (params: Command.CommandOperate) => { export const addCommand = (params: Command.CommandOperate) => {
return http.post<Command.CommandOperate>(`/hosts/command`, params); return http.post<Command.CommandOperate>(`/hosts/command`, params);
}; };

View File

@ -0,0 +1,93 @@
<template>
<div v-loading="loading">
<el-drawer v-model="drawerVisible" :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 @submit.prevent 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.groupID" clearable style="width: 100%">
<div v-for="item in groupList" :key="item.id">
<el-option :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = 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 { GetGroupList } from '@/api/modules/group';
import DrawerHeader from '@/components/drawer-header/index.vue';
const loading = ref();
interface DialogProps {
group: string;
groupType: string;
}
const drawerVisible = ref(false);
const dialogData = ref({
groupID: 0,
groupType: '',
});
const groupList = ref();
const acceptParams = (params: DialogProps): void => {
dialogData.value.groupType = params.groupType;
loadGroups(params.group);
drawerVisible.value = true;
};
const emit = defineEmits(['search', 'change']);
const handleClose = () => {
drawerVisible.value = false;
};
type FormInstance = InstanceType<typeof ElForm>;
const hostInfoRef = ref<FormInstance>();
const rules = reactive({
groupID: [Rules.requiredSelect],
});
const loadGroups = async (groupName: string) => {
const res = await GetGroupList({ type: dialogData.value.groupType });
groupList.value = res.data;
for (const group of groupList.value) {
if (group.name === groupName) {
dialogData.value.groupID = group.id;
break;
}
}
};
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
emit('change', Number(dialogData.value.groupID));
loading.value = false;
drawerVisible.value = false;
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -8,10 +8,22 @@
<el-button type="primary" @click="onCreate()"> <el-button type="primary" @click="onCreate()">
{{ $t('commons.button.create') }}{{ $t('terminal.quickCommand') }} {{ $t('commons.button.create') }}{{ $t('terminal.quickCommand') }}
</el-button> </el-button>
<el-button type="primary" plain @click="onOpenGroupDialog()">
{{ $t('terminal.group') }}
</el-button>
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)"> <el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
{{ $t('commons.button.delete') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </template>
<template #search>
<el-select v-model="group" @change="search()" clearable>
<template #prefix>{{ $t('terminal.group') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<div v-for="item in groupList" :key="item.name">
<el-option :value="item.id" :label="item.name" />
</div>
</el-select>
</template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@ -33,7 +45,14 @@
show-overflow-tooltip show-overflow-tooltip
prop="command" prop="command"
/> />
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix /> <el-table-column
:label="$t('commons.table.group')"
show-overflow-tooltip=""
min-width="100"
prop="groupBelong"
fix
/>
<fu-table-operations width="200px" :buttons="buttons" :label="$t('commons.table.operate')" fix />
</ComplexTable> </ComplexTable>
</template> </template>
</LayoutContent> </LayoutContent>
@ -57,6 +76,13 @@
<el-form-item :label="$t('commons.table.name')" prop="name"> <el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="commandInfo.name" /> <el-input clearable v-model="commandInfo.name" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.table.group')" prop="name">
<el-select filterable v-model="commandInfo.groupID" clearable style="width: 100%">
<div v-for="item in groupList" :key="item.id">
<el-option :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
<el-form-item :label="$t('terminal.command')" prop="command"> <el-form-item :label="$t('terminal.command')" prop="command">
<el-input type="textarea" clearable v-model="commandInfo.command" /> <el-input type="textarea" clearable v-model="commandInfo.command" />
</el-form-item> </el-form-item>
@ -74,12 +100,16 @@
</el-drawer> </el-drawer>
<OpDialog ref="opRef" @search="search" /> <OpDialog ref="opRef" @search="search" />
<GroupDialog @search="loadGroups" ref="dialogGroupRef" />
<GroupChangeDialog @search="search" @change="onChangeGroup" ref="dialogGroupChangeRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Command } from '@/api/interface/command'; import { Command } from '@/api/interface/command';
import GroupDialog from '@/components/group/index.vue';
import OpDialog from '@/components/del-dialog/index.vue'; import OpDialog from '@/components/del-dialog/index.vue';
import GroupChangeDialog from '@/components/group/change.vue';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/host'; import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/host';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import type { ElForm } from 'element-plus'; import type { ElForm } from 'element-plus';
@ -87,10 +117,12 @@ import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { GetGroupList } from '@/api/modules/group';
const loading = ref(); const loading = ref();
const data = ref(); const data = ref();
const selects = ref<any>([]); const selects = ref<any>([]);
const groupList = ref();
const paginationConfig = reactive({ const paginationConfig = reactive({
cacheSizeKey: 'terminal-command-page-size', cacheSizeKey: 'terminal-command-page-size',
currentPage: 1, currentPage: 1,
@ -98,6 +130,8 @@ const paginationConfig = reactive({
total: 0, total: 0,
}); });
const info = ref(); const info = ref();
const group = ref<string>('');
const dialogGroupChangeRef = ref();
const opRef = ref(); const opRef = ref();
@ -111,20 +145,35 @@ let operate = ref<string>('create');
const acceptParams = () => { const acceptParams = () => {
search(); search();
loadGroups();
}; };
const defaultGroupID = ref();
let commandInfo = reactive<Command.CommandOperate>({ let commandInfo = reactive<Command.CommandOperate>({
id: 0, id: 0,
name: '', name: '',
groupID: 0,
command: '', command: '',
}); });
const cmdVisible = ref<boolean>(false); const cmdVisible = ref<boolean>(false);
const loadGroups = async () => {
const res = await GetGroupList({ type: 'command' });
groupList.value = res.data;
for (const group of groupList.value) {
if (group.isDefault) {
defaultGroupID.value = group.id;
break;
}
}
};
const onCreate = async () => { const onCreate = async () => {
commandInfo.id = 0; commandInfo.id = 0;
commandInfo.name = ''; commandInfo.name = '';
commandInfo.command = ''; commandInfo.command = '';
commandInfo.groupID = defaultGroupID.value;
operate.value = 'create'; operate.value = 'create';
cmdVisible.value = true; cmdVisible.value = true;
}; };
@ -133,6 +182,11 @@ const handleClose = () => {
cmdVisible.value = false; cmdVisible.value = false;
}; };
const dialogGroupRef = ref();
const onOpenGroupDialog = () => {
dialogGroupRef.value!.acceptParams({ type: 'command' });
};
const submitAddCommand = (formEl: FormInstance | undefined) => { const submitAddCommand = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
@ -148,14 +202,11 @@ const submitAddCommand = (formEl: FormInstance | undefined) => {
}); });
}; };
const onEdit = async (row: Command.CommandInfo | null) => { const onChangeGroup = async (groupID: number) => {
if (row !== null) { commandInfo.groupID = groupID;
commandInfo.id = row.id; await editCommand(commandInfo);
commandInfo.name = row.name; search();
commandInfo.command = row.command; MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
operate.value = 'edit';
cmdVisible.value = true;
}
}; };
const batchDelete = async (row: Command.CommandInfo | null) => { const batchDelete = async (row: Command.CommandInfo | null) => {
@ -183,10 +234,24 @@ const batchDelete = async (row: Command.CommandInfo | null) => {
}; };
const buttons = [ const buttons = [
{
label: i18n.global.t('terminal.groupChange'),
click: (row: any) => {
commandInfo = row;
dialogGroupChangeRef.value!.acceptParams({
group: row.groupBelong,
groupType: 'command',
});
},
},
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
icon: 'Edit', icon: 'Edit',
click: onEdit, click: (row: any) => {
commandInfo = row;
operate.value = 'edit';
cmdVisible.value = true;
},
}, },
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
@ -199,6 +264,7 @@ const search = async () => {
let params = { let params = {
page: paginationConfig.currentPage, page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize, pageSize: paginationConfig.pageSize,
groupID: Number(group.value),
info: info.value, info: info.value,
}; };
loading.value = true; loading.value = true;

View File

@ -74,20 +74,21 @@
<OpDialog ref="opRef" @search="search" /> <OpDialog ref="opRef" @search="search" />
<OperateDialog @search="search" ref="dialogRef" /> <OperateDialog @search="search" ref="dialogRef" />
<GroupDialog @search="search" ref="dialogGroupRef" /> <GroupDialog @search="search" ref="dialogGroupRef" />
<GroupChangeDialog @search="search" ref="dialogGroupChangeRef" /> <GroupChangeDialog @search="search" @change="onChangeGroup" ref="dialogGroupChangeRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import GroupDialog from '@/components/group/index.vue'; import GroupDialog from '@/components/group/index.vue';
import OpDialog from '@/components/del-dialog/index.vue'; import OpDialog from '@/components/del-dialog/index.vue';
import GroupChangeDialog from '@/views/host/terminal/host/change-group/index.vue'; import GroupChangeDialog from '@/components/group/change.vue';
import OperateDialog from '@/views/host/terminal/host/operate/index.vue'; import OperateDialog from '@/views/host/terminal/host/operate/index.vue';
import { deleteHost, searchHosts } from '@/api/modules/host'; import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/host';
import { GetGroupList } from '@/api/modules/group'; import { GetGroupList } from '@/api/modules/group';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { Host } from '@/api/interface/host'; import { Host } from '@/api/interface/host';
import { MsgSuccess } from '@/utils/message';
const loading = ref(); const loading = ref();
const data = ref(); const data = ref();
@ -102,6 +103,7 @@ const paginationConfig = reactive({
const info = ref(); const info = ref();
const group = ref<string>(''); const group = ref<string>('');
const dialogGroupChangeRef = ref(); const dialogGroupChangeRef = ref();
const currentID = ref();
const opRef = ref(); const opRef = ref();
@ -162,11 +164,22 @@ const loadGroups = async () => {
groupList.value = res.data; groupList.value = res.data;
}; };
const onChangeGroup = async (groupID: number) => {
let param = {
id: currentID.value,
groupID: groupID,
};
await editHostGroup(param);
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
};
const buttons = [ const buttons = [
{ {
label: i18n.global.t('terminal.groupChange'), label: i18n.global.t('terminal.groupChange'),
click: (row: any) => { click: (row: any) => {
dialogGroupChangeRef.value!.acceptParams({ id: row.id, group: row.groupBelong }); currentID.value = row.id;
dialogGroupChangeRef.value!.acceptParams({ group: row.groupBelong, groupType: 'host' });
}, },
}, },
{ {

View File

@ -51,7 +51,14 @@
<div> <div>
<el-select v-model="quickCmd" clearable filterable @change="quickInput" style="width: 25%"> <el-select v-model="quickCmd" clearable filterable @change="quickInput" style="width: 25%">
<template #prefix>{{ $t('terminal.quickCommand') }}</template> <template #prefix>{{ $t('terminal.quickCommand') }}</template>
<el-option v-for="cmd in commandList" :key="cmd.id" :label="cmd.name" :value="cmd.command" /> <el-option-group v-for="group in commandTree" :key="group.label" :label="group.label">
<el-option
v-for="(cmd, index) in group.children"
:key="index"
:label="cmd.name"
:value="cmd.command"
/>
</el-option-group>
</el-select> </el-select>
<el-input v-model="batchVal" @keyup.enter="batchInput" style="width: 75%"> <el-input v-model="batchVal" @keyup.enter="batchInput" style="width: 75%">
<template #prepend> <template #prepend>
@ -74,7 +81,7 @@
</div> </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"
style="margin-top: 5px" style="margin-top: 5px"
clearable clearable
suffix-icon="Search" suffix-icon="Search"
@ -136,8 +143,7 @@ import { ElTree } from 'element-plus';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import i18n from '@/lang'; import i18n from '@/lang';
import { Host } from '@/api/interface/host'; import { Host } from '@/api/interface/host';
import { getHostTree, testByID } from '@/api/modules/host'; import { getCommandTree, getHostTree, testByID } from '@/api/modules/host';
import { getCommandList } from '@/api/modules/host';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import router from '@/routers'; import router from '@/routers';
@ -165,14 +171,14 @@ const terminalValue = ref();
const terminalTabs = ref([]) as any; const terminalTabs = ref([]) as any;
let tabIndex = 0; let tabIndex = 0;
const commandList = ref(); const commandTree = ref();
let quickCmd = ref(); let quickCmd = ref();
let batchVal = ref(); let batchVal = ref();
let isBatch = ref<boolean>(false); let isBatch = ref<boolean>(false);
const popoverRef = ref(); const popoverRef = ref();
const hostfilterInfo = ref(''); const hostFilterInfo = ref('');
const hostTree = ref<Array<Host.HostTree>>(); const hostTree = ref<Array<Host.HostTree>>();
const treeRef = ref<InstanceType<typeof ElTree>>(); const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = { const defaultProps = {
@ -188,7 +194,7 @@ const initCmd = ref('');
const acceptParams = async () => { const acceptParams = async () => {
globalStore.isFullScreen = false; globalStore.isFullScreen = false;
loadCommand(); loadCommandTree();
const res = await getHostTree({}); const res = await getHostTree({});
hostTree.value = res.data; hostTree.value = res.data;
timer = setInterval(() => { timer = setInterval(() => {
@ -257,16 +263,16 @@ const loadHostTree = async () => {
const res = await getHostTree({}); const res = await getHostTree({});
hostTree.value = res.data; hostTree.value = res.data;
}; };
watch(hostfilterInfo, (val: any) => { watch(hostFilterInfo, (val: any) => {
treeRef.value!.filter(val); treeRef.value!.filter(val);
}); });
const filterHost = (value: string, data: any) => { const filterHost = (value: string, data: any) => {
if (!value) return true; if (!value) return true;
return data.label.includes(value); return data.label.includes(value);
}; };
const loadCommand = async () => { const loadCommandTree = async () => {
const res = await getCommandList(); const res = await getCommandTree();
commandList.value = res.data; commandTree.value = res.data || [];
}; };
function quickInput(val: any) { function quickInput(val: any) {