feat(ui): ipam - add batch assign

This commit is contained in:
songlh 2024-11-13 10:03:13 +08:00
parent 6b16d393c7
commit 6532a937bf
5 changed files with 237 additions and 73 deletions

View File

@ -837,7 +837,13 @@ if __name__ == "__main__":
onlineRatio: 'Online Ratio',
scanEnable: 'Scan Enable',
lastScanTime: 'Last Scan Time',
isSuccess: 'Is Success'
isSuccess: 'Is Success',
batchAssign: 'Batch Assign',
batchAssignInProgress: 'Assign in batches, {total} in total, {successNum} successful, {errorNum} failed',
batchAssignCompleted: 'Batch Assign Completed',
batchRecycle: 'Batch Recycle',
batchRecycleInProgress: 'Recycle in batches, {total} in total, {successNum} successful, {errorNum} failed',
batchRecycleCompleted: 'Batch Recycle Completed',
}
}
export default cmdb_en

View File

@ -836,7 +836,13 @@ if __name__ == "__main__":
onlineRatio: '在线率',
scanEnable: '是否扫描',
lastScanTime: '最后扫描时间',
isSuccess: '是否成功'
isSuccess: '是否成功',
batchAssign: '批量分配',
batchAssignInProgress: '正在批量分配,共{total}个,成功{successNum}个,失败{errorNum}个',
batchAssignCompleted: '批量分配已完成',
batchRecycle: '批量回收',
batchRecycleInProgress: '正在批量回收,共{total}个,成功{successNum}个,失败{errorNum}个',
batchRecycleCompleted: '批量回收已完成',
}
}
export default cmdb_zh

View File

@ -3,6 +3,7 @@
:visible="visible"
:width="700"
:title="$t('cmdb.ipam.addressAssign')"
:confirmLoading="confirmLoading"
@ok="handleOk"
@cancel="handleCancel"
>
@ -17,7 +18,7 @@
<a-form-model-item
label="IP"
>
{{ ipData.ip }}
<span class="assign-form-ip" >{{ ipList.join(', ') }}</span>
</a-form-model-item>
<a-form-model-item
v-for="(item) in formList"
@ -80,37 +81,29 @@ export default {
attrList: {
type: Array,
default: () => []
},
subnetData: {
type: Object,
default: () => {}
}
},
data() {
return {
visible: false,
ipData: {},
ipList: [],
nodeId: -1,
formList: [],
form: {},
formRules: {},
statusSelectOption: [
{
value: 0,
label: 'cmdb.ipam.assigned'
},
{
value: 2,
label: 'cmdb.ipam.reserved'
}
]
confirmLoading: false,
isBatch: false
}
},
methods: {
async open({
ipData,
ipList = [],
ipData = null,
nodeId,
}) {
this.isBatch = ipList.length !== 0
this.ipList = ipList.length ? _.cloneDeep(ipList) : [ipData?.ip ?? '']
this.ipData = ipData || {}
this.nodeId = nodeId || -1
this.visible = true
@ -237,7 +230,8 @@ export default {
this.form = {}
this.formRules = {}
this.formList = []
this.visible = false
this.confirmLoading = false
this.isBatch = false
this.$refs.assignFormRef.clearValidate()
},
@ -248,16 +242,35 @@ export default {
return
}
await postIPAMAddress({
ips: [this.ipData.ip],
parent_id: this.nodeId,
...this.form,
subnet_mask: this?.ipData?.subnet_mask ?? undefined,
gateway: this?.ipData?.gateway ?? undefined
})
this.confirmLoading = true
if (!this.isBatch) {
await postIPAMAddress({
ips: this.ipList,
parent_id: this.nodeId,
...this.form,
subnet_mask: this?.ipData?.subnet_mask ?? undefined,
gateway: this?.ipData?.gateway ?? undefined
})
this.$emit('ok')
} else {
const ipChunk = _.chunk(this.ipList, 5)
const paramsList = ipChunk.map((ips) => ({
ips,
parent_id: this.nodeId,
...this.form,
subnet_mask: this?.ipData?.subnet_mask ?? undefined,
gateway: this?.ipData?.gateway ?? undefined
}))
this.$emit('batchAssign', {
paramsList,
ipList: this.ipList
})
}
this.$emit('ok')
this.handleCancel()
this.confirmLoading = false
})
},
@ -280,5 +293,12 @@ export default {
max-height: 400px;
overflow-y: auto;
overflow-x: hidden;
&-ip {
max-height: 100px;
overflow-y: auto;
overflow-x: hidden;
display: block;
}
}
</style>

View File

@ -9,12 +9,11 @@
<div class="address-null-tip2">{{ $t(addressNullTip) }}</div>
</div>
<div v-else-if="loading" class="address-loading">
<a-icon type="loading" class="address-loading-icon" />
<span class="address-loading-text">{{ $t('loading') }}</span>
</div>
<template v-else>
<a-spin
v-else
:tip="loadTip"
:spinning="loading"
>
<div class="address-header">
<div class="address-header-left">
<a-input-search
@ -53,6 +52,15 @@
</a-select-option>
</a-select>
<div v-if="selectedIPList.length" class="ops-list-batch-action">
<span @click="clickBatchAssign">{{ $t('cmdb.ipam.batchAssign') }}</span>
<a-divider type="vertical" />
<span @click="clickBatchRecycle">{{ $t('cmdb.ipam.batchRecycle') }}</span>
<a-divider type="vertical" />
<span @click="handleExport">{{ $t('export') }}</span>
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedIPList.length }) }}</span>
</div>
<div
v-if="currentLayout === 'grid'"
class="address-header-status"
@ -85,22 +93,12 @@
</div>
<div class="address-header-right">
<a-button
type="primary"
class="ops-button-ghost"
ghost
@click="handleExport"
>
<ops-icon type="veops-export" />
{{ $t('export') }}
</a-button>
<div class="address-header-layout">
<div
v-for="(item) in layoutList"
:key="item.value"
:class="['address-header-layout-item', currentLayout === item.value ?'address-header-layout-item-active' : '']"
@click="currentLayout = item.value"
@click="handleChangeLayout(item.value)"
>
<ops-icon :type="item.icon" />
</div>
@ -119,6 +117,7 @@
:columnWidth="columnWidth"
@openAssign="openAssign"
@recycle="handleRecycle"
@selectChange="handleTableSelectChange"
/>
<GridIP
@ -131,13 +130,13 @@
@recycle="handleRecycle"
/>
</div>
</template>
</a-spin>
<AssignForm
ref="assignFormRef"
:attrList="attrList"
:subnetData="subnetData"
@ok="getIPList"
@batchAssign="batchAssign"
/>
</div>
</template>
@ -188,6 +187,8 @@ export default {
referenceCIIdMap: {},
columnWidth: {},
loading: false,
selectedIPList: [],
loadTip: this.$t('loading'),
currentStatus: 'all',
filterOption: [
@ -298,6 +299,7 @@ export default {
},
methods: {
async initData() {
this.loadTip = this.$t('loading')
this.loading = true
try {
await this.getColumns()
@ -497,6 +499,7 @@ export default {
let tableData = []
if (this.currentLayout === 'table') {
tableData = this.$refs.tableIPRef.getCheckedTableData()
this.selectedIPList = []
} else {
tableData = this.filterIPList
}
@ -561,6 +564,143 @@ export default {
})
},
})
},
handleChangeLayout(value) {
if (this.currentLayout !== value) {
if (value === 'grid') {
this.selectedIPList = []
}
this.currentLayout = value
}
},
handleTableSelectChange(ips) {
this.selectedIPList = ips
},
clickBatchAssign() {
this.$refs.assignFormRef.open({
nodeId: this?.nodeData?._id,
ipData: {
subnet_mask: this?.subnetData?.subnet_mask ?? undefined,
gateway: this?.subnetData?.gateway ?? undefined
},
ipList: this.selectedIPList
})
},
async batchAssign({
paramsList,
ipList
}) {
let successNum = 0
let errorNum = 0
try {
this.loading = true
this.loadTip = this.$t('cmdb.ipam.batchAssignInProgress', {
total: ipList.length,
successNum: successNum,
errorNum: errorNum,
})
await _.reduce(
paramsList,
(promiseChain, params) => {
const ipCount = params?.ips?.length ?? 0
return promiseChain.then(() => {
return postIPAMAddress(params).then(() => {
successNum += ipCount
}).catch(() => {
errorNum += ipCount
}).finally(() => {
this.loadTip = this.$t('cmdb.ipam.batchAssignInProgress', {
total: ipList.length,
successNum: successNum,
errorNum: errorNum,
})
})
})
},
Promise.resolve()
)
if (this.$refs.tableIPRef) {
this.$refs.tableIPRef.clearCheckbox()
this.selectedIPList = []
}
this.$message.success(this.$t('cmdb.ipam.batchAssignCompleted'))
this.loading = false
this.getIPList()
} catch (error) {
console.log('error', error)
}
},
clickBatchRecycle() {
this.$confirm({
title: this.$t('warning'),
content: this.$t('cmdb.ipam.recycleTip'),
onOk: () => {
this.handleBatchRecycle()
},
})
},
async handleBatchRecycle() {
let successNum = 0
let errorNum = 0
try {
this.loading = true
this.loadTip = this.$t('cmdb.ipam.batchRecycleInProgress', {
total: this.selectedIPList.length,
successNum: successNum,
errorNum: errorNum,
})
const ipChunk = _.chunk(this.selectedIPList, 5)
await _.reduce(
ipChunk,
(promiseChain, ips) => {
const ipCount = ips.length
console.log('ipCount', ipCount, successNum, errorNum)
return promiseChain.then(() => {
return postIPAMAddress({
ips,
parent_id: this.nodeData._id,
assign_status: 1
}).then(() => {
successNum += ipCount
}).catch(() => {
errorNum += ipCount
}).finally(() => {
this.loadTip = this.$t('cmdb.ipam.batchRecycleInProgress', {
total: this.selectedIPList.length,
successNum: successNum,
errorNum: errorNum,
})
})
})
},
Promise.resolve()
)
if (this.$refs.tableIPRef) {
this.$refs.tableIPRef.clearCheckbox()
this.selectedIPList = []
}
this.$message.success(this.$t('cmdb.ipam.batchRecycleCompleted'))
this.loading = false
this.getIPList()
} catch (error) {
console.log('error', error)
}
}
}
}
@ -570,7 +710,6 @@ export default {
.address {
width: 100%;
height: fit-content;
position: relative;
&-header {
width: 100%;
@ -695,27 +834,5 @@ export default {
color: #2F54EB;
}
}
&-loading {
width: 100%;
height: 300px;
position: absolute;
top: 0;
left: 0;
color: #000000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
&-icon {
font-size: 28px;
}
&-text {
margin-top: 12px;
}
}
}
</style>

View File

@ -14,7 +14,7 @@
class="ops-unstripe-table checkbox-hover-table"
@checkbox-change="onSelectChange"
@checkbox-all="onSelectChange"
@checkbox-range-end="onSelectChange"
@checkbox-range-end="onSelectRangeEnd"
>
<vxe-table-column
align="center"
@ -241,13 +241,20 @@ export default {
}
if (clearCheckbox) {
tableRef.clearCheckboxRow()
tableRef.clearCheckboxReserve()
this.clearCheckbox()
}
return tableData
},
clearCheckbox() {
const tableRef = this.$refs?.xTable?.getVxetableRef?.()
if (tableRef) {
tableRef.clearCheckboxRow()
tableRef.clearCheckboxReserve()
}
},
getReferenceAttrValue(id, col) {
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
if (!ci) {
@ -267,7 +274,15 @@ export default {
},
onSelectChange() {
console.log('onSelectChange')
const xTable = this.$refs.xTable.getVxetableRef()
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
const ips = records.map((item) => item.ip)
this.$emit('selectChange', ips)
},
onSelectRangeEnd({ records }) {
const ips = records?.map?.((item) => item.ip) || []
this.$emit('selectChange', ips)
},
}
}