646 lines
22 KiB
Vue
646 lines
22 KiB
Vue
<template>
|
||
<div class="wrapper-container">
|
||
<el-row :gutter="10">
|
||
<el-col :span="6" style="width: 286px">
|
||
<el-card class="group">
|
||
<div slot="header">
|
||
分组管理
|
||
<el-tooltip class="item" effect="dark" content="添加根节点" placement="top-start">
|
||
<el-button class="pull-right" type="text" @click="add('0')" icon="el-icon-plus"></el-button>
|
||
</el-tooltip>
|
||
</div>
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<el-tree :data="treeData" ref="scriptTree" node-key="id" :props="defaultProps" :highlight-current="true" :check-on-click-node="true" @node-click="handleNodeClick">
|
||
<span class="custom-tree-node" slot-scope="{ node, data }">
|
||
<div class="custom-tree-node-wrapper">
|
||
<span class="custom-tree-node-label">
|
||
{{ node.label }}
|
||
</span>
|
||
<span class="operate-btns">
|
||
<dot-dropdown :events="dropMenuEvents" :data="{ node, data }" @addNode="addNode" @editNode="editNode" @removeNode="removeNode" />
|
||
</span>
|
||
</div>
|
||
</span>
|
||
</el-tree>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="18" style="width: calc(100% - 286px)">
|
||
<el-card>
|
||
<el-tabs v-model="typeName" @tab-click="handleSearch">
|
||
<el-tab-pane name="Ansible" label="Ansible">
|
||
<cb-advance-table :data="list" :card-border="false" :searchConfigs="searchConfigs" :params="params" :total="total" :columns="ansibleColumns" :get-list="getList" :loading="loading" ref="ansibleTableRef" @select="handleSelectItem" @select-all="handleSelectAll">
|
||
<!-- <template #action>
|
||
<el-button type="primary" :disabled="!selectList.length" @click.native="handleConfig({}, 'exclude')"> 排除 </el-button>
|
||
<el-button type="primary" :disabled="!selectList.length" @click.native="handleConfig({}, 'recovery')"> 恢复 </el-button>
|
||
<el-button type="primary" @click.native="handleDeployGaia"> 扩容 </el-button>
|
||
</template> -->
|
||
<template #name="val, record">
|
||
<span class="detail-href" @click="getDetail(record.id)">{{ val }}</span>
|
||
</template>
|
||
<template #isSuper="val">
|
||
{{ val == null ? '--' : val ? '是' : '否' }}
|
||
</template>
|
||
<template #deleted="deleted">
|
||
{{ deleted == null ? '--' : deleted ? '异常' : '正常' }}
|
||
</template>
|
||
<template #isExclude="val">
|
||
{{ val == null ? '--' : val ? '是' : '否' }}
|
||
</template>
|
||
<template #level="level">
|
||
<cb-status-icon :type="level | levelFilter('color')">{{ level | levelFilter('name') }} </cb-status-icon>
|
||
</template>
|
||
<template #operate="val, record">
|
||
<el-button type="text" @click.native="setting(record)" :disabled="record.deleted"> 配置 </el-button>
|
||
<!-- <div class="action-divider"></div>
|
||
<el-button type="text" @click.native="handleConfig(record)" :disabled="record.deleted"> {{ record.isExclude ? '恢复' : '排除' }} </el-button>
|
||
<div class="action-divider"></div>
|
||
<el-dropdown trigger="click">
|
||
<span class="el-dropdown-link"> 更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click.native="handleDelGaia(record)" :disabled="record.deleted"> 缩容 </el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown> -->
|
||
</template>
|
||
</cb-advance-table>
|
||
</el-tab-pane>
|
||
<!-- <el-tab-pane name="SaltMaster" label="SaltMaster">
|
||
<cb-advance-table
|
||
:card-border="false"
|
||
:data="saltMasterList"
|
||
:searchConfigs="saltMasterSearchConfigs"
|
||
:params="paramt"
|
||
:total="saltMasterTotal"
|
||
:columns="saltMasterColumns"
|
||
:get-list="getSaltMasterList"
|
||
:loading="saltMasterLoading"
|
||
ref="saltTableRef"
|
||
@select="handleSelectItem"
|
||
@select-all="handleSelectAll"
|
||
>
|
||
<template #action>
|
||
<el-button @click="handleCreate(null, 1)" type="primary" icon="el-icon-plus">新增 </el-button>
|
||
</template>
|
||
<template #name="val, record">
|
||
<span class="detail-href" @click="getDetail(record.id)">{{ val }}</span>
|
||
</template>
|
||
<template #level="level">
|
||
<cb-status-icon :type="level | levelFilter('color')">{{ level | levelFilter('name') }} </cb-status-icon>
|
||
</template>
|
||
<template #operate="val, record">
|
||
<el-button icon="el-icon-edit" type="text" @click="handleCreate(record, 2)"> 编辑 </el-button>
|
||
<div class="action-divider"></div>
|
||
<el-button icon="el-icon-delete" type="text" @click="handleDelete(record.id)"> 删除 </el-button>
|
||
<div class="action-divider"></div>
|
||
<el-dropdown trigger="click">
|
||
<span class="el-dropdown-link"> 更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
|
||
<el-dropdown-menu slot="dropdown">
|
||
<el-dropdown-item @click.native="syncMinion(record)"> minion同步 </el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</el-dropdown>
|
||
</template>
|
||
</cb-advance-table>
|
||
</el-tab-pane> -->
|
||
</el-tabs>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
<!--新增编辑worker分组-->
|
||
<cb-dialog :title="textMap1[dialogStatus]" :close-on-click-modal="false" v-if="addScriptGroupVisible" :visible.sync="addScriptGroupVisible" width="35%">
|
||
<cb-form :model="addScriptGroup" ref="addScriptGroup">
|
||
<cb-form-item label="分组名称:" prop="name" validate="required">
|
||
<el-input v-model="addScriptGroup.name" auto-complete="off"></el-input>
|
||
</cb-form-item>
|
||
<cb-form-item label="分组描述:" prop="remark">
|
||
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 6 }" v-model="addScriptGroup.remark" auto-complete="off"></el-input>
|
||
</cb-form-item>
|
||
</cb-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="ghost" @click.native="addScriptGroupVisible = false">取消</el-button>
|
||
<el-button type="primary" @click.native="addScriptGroupSubmit">确定</el-button>
|
||
</div>
|
||
</cb-dialog>
|
||
<!--新增编辑worker-->
|
||
<cb-dialog :title="textMap2[dialogStatus]" :close-on-click-modal="false" v-if="addWorkerVisible" :visible.sync="addWorkerVisible" width="35%">
|
||
<cb-form :model="addWorkerData" ref="addWorkerData" label-width="150px">
|
||
<cb-form-item label="SaltMaster地址:" prop="instance" validate="required">
|
||
<el-input v-model="addWorkerData.instance" auto-complete="off"></el-input>
|
||
</cb-form-item>
|
||
<cb-form-item label="协议类型:" prop="protocol" validate="required">
|
||
<el-input v-model="addWorkerData.protocol" auto-complete="off"></el-input>
|
||
</cb-form-item>
|
||
<cb-form-item label="用户名:" prop="username" validate="required">
|
||
<el-input v-model="addWorkerData.username"></el-input>
|
||
</cb-form-item>
|
||
<cb-form-item label="密码:" prop="password" validate="required">
|
||
<el-input v-model="addWorkerData.password" show-password auto-complete="new-password"></el-input>
|
||
</cb-form-item>
|
||
</cb-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="ghost" @click.native="addWorkerVisible = false">取消</el-button>
|
||
<el-button type="primary" @click.native="addWorkerSubmit">确定</el-button>
|
||
</div>
|
||
</cb-dialog>
|
||
<set-group :set-data="setData" v-if="setData.dialog" @setOk="handleSearch"></set-group>
|
||
<GaiaDialog :dialog="gaiaDialog" v-if="gaiaDialog.visible" @back="handleSearch"></GaiaDialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import SetGroup from '@/views/resource-manage/components/setGroup'
|
||
import { createBsmHostGroup, deleteBsmHostGroup, getBsmHostGroup, getWorkers, getWorkerDetail, updateBsmHostGroup, createBsmHostWorker, updateBsmHostWorker, deleteBsmHostWorker, updateWorkersConfig, syncMinionApi } from '@/services/task/resource'
|
||
import DotDropdown from 'views/repository/component/dotDropdown.vue'
|
||
import crypto from 'utils/crypto.js'
|
||
import GaiaDialog from './GaiaDialog.vue'
|
||
export default {
|
||
components: {
|
||
SetGroup,
|
||
DotDropdown,
|
||
GaiaDialog,
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
searchConfigs: [
|
||
{ label: '实例', value: 'instance', type: 'Input' },
|
||
{ value: 'category', initValue: 'Ansible', type: 'Const' },
|
||
],
|
||
saltMasterSearchConfigs: [
|
||
{ label: '实例', value: 'instance', type: 'Input' },
|
||
{ value: 'category', initValue: 'SaltMaster', type: 'Const' },
|
||
],
|
||
ansibleColumns: [
|
||
{
|
||
type: 'selection',
|
||
selectable(row, index) {
|
||
return !row.deleted
|
||
},
|
||
},
|
||
{ label: '实例', prop: 'instance', scopedSlots: { customRender: 'instance' } },
|
||
{ label: '心跳状态', prop: 'deleted', scopedSlots: { customRender: 'deleted' } },
|
||
{ label: '超级worker', prop: 'isSuper', scopedSlots: { customRender: 'isSuper' } },
|
||
{ label: '所属分组', prop: 'groupName' },
|
||
{ label: '任务数量', prop: 'tasks' },
|
||
{ label: '是否排除', prop: 'isExclude', scopedSlots: { customRender: 'isExclude' } },
|
||
{ label: '操作', disabled: true, width: '200px', scopedSlots: { customRender: 'operate' } },
|
||
],
|
||
saltMasterColumns: [
|
||
{ type: 'selection' },
|
||
{ label: '实例', prop: 'instance', scopedSlots: { customRender: 'instance' } },
|
||
{ label: '类型', prop: 'category' },
|
||
{ label: '协议类型', prop: 'protocol' },
|
||
{ label: '所属分组', prop: 'groupName' },
|
||
{ label: '操作', disabled: true, width: '200px', scopedSlots: { customRender: 'operate' } },
|
||
],
|
||
list: [],
|
||
saltMasterList: [],
|
||
saltMasterLoading: false,
|
||
total: null,
|
||
saltMasterTotal: null,
|
||
paramt: {
|
||
page: 1,
|
||
rows: 20,
|
||
},
|
||
params: {
|
||
page: 1,
|
||
rows: 20,
|
||
},
|
||
listQuery: {
|
||
name: '',
|
||
version: '',
|
||
},
|
||
groupId: null,
|
||
parentId: null,
|
||
nodedata: {},
|
||
// 树形菜单
|
||
treeData: [],
|
||
defaultProps: {
|
||
children: 'childrenList',
|
||
label: 'name',
|
||
},
|
||
dropMenuEvents: [
|
||
{ label: '编辑', funcName: 'editNode' },
|
||
{ label: '删除', funcName: 'removeNode' },
|
||
// { label: '新建子节点', funcName: 'addNode' }
|
||
],
|
||
// 详情
|
||
detailFlag: false,
|
||
detailData: [],
|
||
// 新增编辑worker分组
|
||
dialogStatus: '',
|
||
textMap1: {
|
||
update: '编辑分组',
|
||
create: '新增分组',
|
||
},
|
||
addScriptGroup: {},
|
||
addScriptGroupVisible: false,
|
||
props: {
|
||
value: 'id',
|
||
children: 'childrenList',
|
||
label: 'name',
|
||
checkStrictly: true,
|
||
},
|
||
addScript: {
|
||
name: '',
|
||
},
|
||
flag: null,
|
||
// 树
|
||
operate: false,
|
||
chartObject: {},
|
||
setData: {
|
||
dialog: false,
|
||
data: {},
|
||
},
|
||
// 新增编辑worker
|
||
textMap2: {
|
||
update: '编辑代理',
|
||
create: '新增代理',
|
||
},
|
||
addWorkerVisible: false,
|
||
addWorkerData: {},
|
||
typeName: 'Ansible',
|
||
selectList: [],
|
||
idList: [],
|
||
timer: null,
|
||
gaiaDialog: {
|
||
visible: false,
|
||
record: {},
|
||
},
|
||
}
|
||
},
|
||
created() {
|
||
this.getTreeData()
|
||
this.startInterval()
|
||
// this.handleSearch()
|
||
},
|
||
destroyed() {
|
||
clearInterval(this.timer)
|
||
},
|
||
methods: {
|
||
startInterval() {
|
||
this.timer = setInterval(this.handleSearch, 1000 * 10)
|
||
},
|
||
addNode({ node, data }) {
|
||
this.addScriptGroup = {}
|
||
this.dialogStatus = 'create'
|
||
this.parentId = data.id
|
||
this.addScriptGroupVisible = true
|
||
},
|
||
editNode({ node, data }) {
|
||
this.addScriptGroup = Object.assign({}, data)
|
||
this.dialogStatus = 'update'
|
||
this.addScriptGroupVisible = true
|
||
},
|
||
removeNode({ node, data }) {
|
||
this.$confirm('您确定要删除该分组吗?', '提示', {
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
deleteBsmHostGroup(data.id).then((data) => {
|
||
if (data.success) {
|
||
this.$message.success({
|
||
message: data.message,
|
||
type: 'success',
|
||
})
|
||
this.getTreeData()
|
||
this.groupId = null
|
||
}
|
||
})
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
setting(row) {
|
||
this.setData = {
|
||
dialog: true,
|
||
data: {
|
||
...row,
|
||
},
|
||
}
|
||
},
|
||
syncMinion({ id }) {
|
||
this.$confirm('您确定要执行同步吗?', '提示', {
|
||
type: 'warning',
|
||
})
|
||
.then(async () => {
|
||
const res = await syncMinionApi(id)
|
||
if (res.success) {
|
||
this.$message.success(res.message)
|
||
this.getSaltMasterList()
|
||
}
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
async getTreeData() {
|
||
const that = this
|
||
const data = await getBsmHostGroup({ parentId: 0 })
|
||
if (data.success) {
|
||
that.treeData = data.data
|
||
setTimeout(function () {
|
||
if (that.groupId) {
|
||
that.$refs.scriptTree.setCurrentKey(that.groupId)
|
||
}
|
||
}, 10)
|
||
}
|
||
},
|
||
async getList() {
|
||
this.selectList = []
|
||
this.refreshId()
|
||
this.loading = true
|
||
const data = await getWorkers(this.params)
|
||
if (data.success) {
|
||
this.list = data.data.rows
|
||
this.total = data.data.total
|
||
this.loading = false
|
||
}
|
||
},
|
||
handleNodeClick(node) {
|
||
this.groupId = node.id
|
||
this.nodedata = node
|
||
this.handleSearch()
|
||
},
|
||
// 查询
|
||
handleSearch() {
|
||
this.selectList = []
|
||
this.refreshId()
|
||
const { ansibleTableRef, saltTableRef } = this.$refs
|
||
if (this.typeName == 'Ansible') ansibleTableRef.handleSearch()
|
||
else saltTableRef.handleSearch()
|
||
},
|
||
// 新增worker分组
|
||
add(flag) {
|
||
this.flag = flag
|
||
this.addScriptGroup = {}
|
||
this.dialogStatus = 'create'
|
||
this.parentId = '0'
|
||
this.addScriptGroupVisible = true
|
||
},
|
||
// 保存新增的worker分组
|
||
addScriptGroupSubmit() {
|
||
const that = this
|
||
this.$refs.addScriptGroup.validate((valid) => {
|
||
if (valid) {
|
||
const editObj = ['name', 'code', 'remark']
|
||
const param = {}
|
||
for (const a in editObj) {
|
||
const attr = editObj[a]
|
||
param[attr] = that.addScriptGroup[attr]
|
||
}
|
||
let service = ''
|
||
if (that.dialogStatus === 'update') {
|
||
service = updateBsmHostGroup
|
||
param.id = this.groupId
|
||
} else {
|
||
service = createBsmHostGroup
|
||
}
|
||
service(param).then((data) => {
|
||
if (data.success) {
|
||
that.$notify({
|
||
message: data.message,
|
||
type: 'success',
|
||
})
|
||
that.addScriptGroupVisible = false
|
||
if (that.flag === '2') {
|
||
that.nodedata = param
|
||
}
|
||
that.getTreeData()
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
// 详情
|
||
getDetail(id) {
|
||
this.listQuery.version = ''
|
||
this.activeName = 'storage'
|
||
getWorkerDetail(id).then((data) => {
|
||
if (data.success) {
|
||
this.addScript = Object.assign({}, data.data)
|
||
this.addScript.id = id
|
||
this.addScript.groupIds = [this.addScript.groupIds]
|
||
this.detailFlag = true
|
||
}
|
||
})
|
||
},
|
||
goBack() {
|
||
this.detailFlag = false
|
||
},
|
||
async getSaltMasterList() {
|
||
this.selectList = []
|
||
this.refreshId()
|
||
this.saltMasterLoading = true
|
||
const data = await getWorkers(this.paramt)
|
||
if (data.success) {
|
||
this.saltMasterList = data.data.rows
|
||
this.saltMasterTotal = data.data.total
|
||
this.saltMasterLoading = false
|
||
}
|
||
},
|
||
// 新增编辑
|
||
handleCreate(row, flag) {
|
||
this.addWorkerData = {
|
||
content: '',
|
||
}
|
||
this.source = '1'
|
||
switch (flag) {
|
||
case 1:
|
||
this.dialogStatus = 'create'
|
||
this.addWorkerData = {
|
||
instance: '',
|
||
category: 'SaltMaster',
|
||
protocol: '',
|
||
username: '',
|
||
password: '',
|
||
}
|
||
break
|
||
case 2:
|
||
this.dialogStatus = 'update'
|
||
this.addWorkerData = row
|
||
this.addWorkerData.password = crypto.decrypt(this.addWorkerData.password)
|
||
break
|
||
}
|
||
this.addWorkerVisible = true
|
||
},
|
||
handleDelete(id) {
|
||
this.$confirm('您确定要删除该网段吗?', '提示', {
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
deleteBsmHostWorker(id).then((data) => {
|
||
if (data.success) {
|
||
this.$message.success({
|
||
message: data.message,
|
||
type: 'success',
|
||
})
|
||
this.getSaltMasterList()
|
||
}
|
||
})
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
// 保存worker
|
||
addWorkerSubmit() {
|
||
const that = this
|
||
this.$refs.addWorkerData.validate((valid) => {
|
||
if (valid) {
|
||
const password = crypto.encrypt(this.addWorkerData.password)
|
||
let service
|
||
switch (this.dialogStatus) {
|
||
case 'create':
|
||
service = createBsmHostWorker
|
||
break
|
||
case 'update':
|
||
service = updateBsmHostWorker
|
||
break
|
||
}
|
||
service({ ...this.addWorkerData, password }).then((data) => {
|
||
if (data.success) {
|
||
this.$message({
|
||
message: data.message,
|
||
type: 'success',
|
||
})
|
||
that.addWorkerVisible = false
|
||
that.getSaltMasterList()
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
refreshId() {
|
||
this.idList = []
|
||
this.selectList.forEach((item) => {
|
||
this.idList.push(item.id)
|
||
})
|
||
},
|
||
// 单选
|
||
handleSelectItem(selection, row) {
|
||
this.refreshId()
|
||
if (this.idList.indexOf(row.id) > -1) {
|
||
for (let j = 0; j < this.selectList.length; j++) {
|
||
const item = this.selectList[j]
|
||
if (item.id == row.id) {
|
||
this.selectList.splice(j, 1)
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
this.selectList.push(row)
|
||
}
|
||
},
|
||
// 全选
|
||
handleSelectAll(selection) {
|
||
this.refreshId()
|
||
if (selection.length) {
|
||
// 全选情况下
|
||
selection.forEach((item) => {
|
||
if (this.idList.indexOf(item.id) == -1) {
|
||
this.selectList.push(item)
|
||
}
|
||
})
|
||
} else {
|
||
// 全不选情况下
|
||
this.list.forEach((item) => {
|
||
if (this.idList.indexOf(item.id) > -1) {
|
||
for (let j = 0; j < this.selectList.length; j++) {
|
||
const row = this.selectList[j]
|
||
if (item.id == row.id) {
|
||
this.selectList.splice(j, 1)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
},
|
||
// 排除恢复
|
||
handleConfig(row, type) {
|
||
this.refreshId()
|
||
const { isExclude = type } = row
|
||
this.$confirm(`您确定要${(!type && isExclude) || type == 'recovery' ? '恢复' : '排除'}吗?`, '提示', {
|
||
confirmButtonClass: 'el-button--danger',
|
||
type: 'warning',
|
||
})
|
||
.then(async () => {
|
||
const { success, message } = await updateWorkersConfig((!type && isExclude) || type == 'recovery' ? 'recovery' : 'exclude', {
|
||
workerIds: type ? this.idList : [row.id],
|
||
})
|
||
if (!success) return
|
||
this.$message.success({
|
||
message: message,
|
||
type: 'success',
|
||
})
|
||
this.selectList = []
|
||
this.getList()
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleDeployGaia() {
|
||
this.gaiaDialog = {
|
||
visible: true,
|
||
record: {},
|
||
isDeploy: true,
|
||
}
|
||
},
|
||
handleDelGaia(row) {
|
||
this.gaiaDialog = {
|
||
visible: true,
|
||
record: { ...row },
|
||
}
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style scoped="scoped" lang="scss">
|
||
.code-type {
|
||
height: 35px;
|
||
padding-left: 15px;
|
||
line-height: 35px;
|
||
border: 1px solid #ddd;
|
||
}
|
||
.operate {
|
||
position: absolute !important;
|
||
z-index: 99;
|
||
}
|
||
.content {
|
||
font-size: 14px;
|
||
border: 1px solid #cccccc;
|
||
min-height: 50px;
|
||
max-height: 300px;
|
||
padding: 10px;
|
||
overflow: auto;
|
||
}
|
||
.item {
|
||
color: rgb(64 158 255);
|
||
font-size: 16px;
|
||
}
|
||
::v-deep .tree .el-tree-node__expand-icon.expanded {
|
||
-webkit-transform: rotate(0deg);
|
||
transform: rotate(0deg);
|
||
}
|
||
::v-deep .el-icon-caret-right:before {
|
||
content: '\e6e0';
|
||
font-size: 14px;
|
||
}
|
||
::v-deep .el-tree-node__content {
|
||
position: relative;
|
||
height: 32px;
|
||
line-height: 32px;
|
||
.operate-btns {
|
||
position: absolute;
|
||
right: 2px;
|
||
display: none;
|
||
}
|
||
// 鼠标悬停时,展示
|
||
&:hover,
|
||
:focus-within {
|
||
.operate-btns {
|
||
display: inline;
|
||
}
|
||
}
|
||
}
|
||
</style>
|