UI: batch update relation

This commit is contained in:
pycook 2020-04-01 11:09:41 +08:00
parent cf5e4966c0
commit 486fcac138
12 changed files with 204 additions and 15 deletions

View File

@ -485,7 +485,7 @@ class CIRelationManager(object):
return numfound, len(first_ci_ids), result return numfound, len(first_ci_ids), result
@classmethod @classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None): def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, many_to_one=False):
first_ci = CIManager.confirm_ci_existed(first_ci_id) first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id) second_ci = CIManager.confirm_ci_existed(second_ci_id)
@ -495,7 +495,7 @@ class CIRelationManager(object):
to_dict=False, to_dict=False,
first=True) first=True)
if existed is not None: if existed is not None:
if existed.relation_type_id != relation_type_id: if existed.relation_type_id != relation_type_id and relation_type_id is not None:
existed.update(relation_type_id=relation_type_id) existed.update(relation_type_id=relation_type_id)
CIRelationHistoryManager().add(existed, OperateType.UPDATE) CIRelationHistoryManager().add(existed, OperateType.UPDATE)
@ -509,6 +509,16 @@ class CIRelationManager(object):
relation_type_id or abort(404, "Relation {0} <-> {1} is not found".format( relation_type_id or abort(404, "Relation {0} <-> {1} is not found".format(
first_ci.ci_type.name, second_ci.ci_type.name)) first_ci.ci_type.name, second_ci.ci_type.name))
if many_to_one:
for item in CIRelation.get_by(second_ci_id=second_ci_id,
relation_type_id=relation_type_id,
to_dict=False):
item.soft_delete()
his_manager = CIRelationHistoryManager()
his_manager.add(item, operate_type=OperateType.DELETE)
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
existed = CIRelation.create(first_ci_id=first_ci_id, existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id, second_ci_id=second_ci_id,
relation_type_id=relation_type_id) relation_type_id=relation_type_id)
@ -544,3 +554,25 @@ class CIRelationManager(object):
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE) ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
return cls.delete(cr.id) return cls.delete(cr.id)
@classmethod
def batch_update(cls, ci_ids, parents):
"""
only for many to one
:param ci_ids:
:param parents:
:return:
"""
from api.lib.cmdb.utils import TableMap
if parents is not None and isinstance(parents, dict):
for attr_name in parents:
if parents[attr_name]:
attr = AttributeCache.get(attr_name)
value_table = TableMap(attr_name=attr.name).table
parent = value_table.get_by(attr_id=attr.id, value=parents[attr_name], first=True, to_dict=False)
if not parent:
return abort(404, "{0}: {1} is not found".format(attr_name, parents[attr_name]))
parent_id = parent.ci_id
for ci_id in ci_ids:
cls.add(parent_id, ci_id, many_to_one=True)

View File

@ -11,7 +11,7 @@ from api.extensions import celery
from api.extensions import db from api.extensions import db
from api.extensions import es from api.extensions import es
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import CITypeAttributeCache from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
@ -81,7 +81,7 @@ def ci_type_attribute_order_rebuild(type_id):
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
attrs = CITypeAttributeCache.get(type_id) attrs = CITypeAttributesCache.get(type_id)
id2attr = {attr.attr_id: attr for attr in attrs} id2attr = {attr.attr_id: attr for attr in attrs}
res = CITypeAttributeGroupManager.get_by_type_id(type_id, True) res = CITypeAttributeGroupManager.get_by_type_id(type_id, True)

View File

@ -11,6 +11,7 @@ from api.lib.cmdb.cache import RelationTypeCache
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci_relation.search import Search from api.lib.cmdb.search.ci_relation.search import Search
from api.lib.decorator import args_required
from api.lib.perm.auth import auth_abandoned from api.lib.perm.auth import auth_abandoned
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
@ -122,11 +123,13 @@ class CIRelationView(APIView):
def post(self, first_ci_id, second_ci_id): def post(self, first_ci_id, second_ci_id):
manager = CIRelationManager() manager = CIRelationManager()
res = manager.add(first_ci_id, second_ci_id) res = manager.add(first_ci_id, second_ci_id)
return self.jsonify(cr_id=res) return self.jsonify(cr_id=res)
def delete(self, first_ci_id, second_ci_id): def delete(self, first_ci_id, second_ci_id):
manager = CIRelationManager() manager = CIRelationManager()
manager.delete_2(first_ci_id, second_ci_id) manager.delete_2(first_ci_id, second_ci_id)
return self.jsonify(message="CIType Relation is deleted") return self.jsonify(message="CIType Relation is deleted")
@ -136,4 +139,24 @@ class DeleteCIRelationView(APIView):
def delete(self, cr_id): def delete(self, cr_id):
manager = CIRelationManager() manager = CIRelationManager()
manager.delete(cr_id) manager.delete(cr_id)
return self.jsonify(message="CIType Relation is deleted") return self.jsonify(message="CIType Relation is deleted")
class BatchCreateOrUpdateCIRelationView(APIView):
url_prefix = "/ci_relations/batch"
@args_required('ci_ids')
@args_required('parents')
def post(self):
ci_ids = request.values.get('ci_ids')
parents = request.values.get('parents')
CIRelationManager.batch_update(ci_ids, parents)
return self.jsonify(code=200)
@args_required('ci_ids')
@args_required('parents')
def put(self):
return self.post()

View File

@ -15,7 +15,7 @@
"@antv/data-set": "^0.10.2", "@antv/data-set": "^0.10.2",
"@handsontable-pro/vue": "^3.1.1", "@handsontable-pro/vue": "^3.1.1",
"@handsontable/vue": "^4.1.1", "@handsontable/vue": "^4.1.1",
"ant-design-vue": "1.5.0-beta.1", "ant-design-vue": "1.5.0",
"axios": "^0.19.0", "axios": "^0.19.0",
"core-js": "^3.1.2", "core-js": "^3.1.2",
"enquire.js": "^2.1.6", "enquire.js": "^2.1.6",

View File

@ -1,9 +1,9 @@
<template> <template>
<a-locale-provider :locale="locale"> <a-config-provider :locale="locale">
<div id="app"> <div id="app">
<router-view v-if="alive" /> <router-view v-if="alive" />
</div> </div>
</a-locale-provider> </a-config-provider>
</template> </template>
<script> <script>

View File

@ -28,3 +28,11 @@ export function statisticsCIRelation (params) {
params: params params: params
}) })
} }
export function batchUpdateCIRelation (ciIds, parents) {
return axios({
url: '/v0.1/ci_relations/batch',
method: 'POST',
data: { ci_ids: ciIds, parents: parents }
})
}

View File

@ -8,7 +8,7 @@
*/ */
import Vue from 'vue' import Vue from 'vue'
import { import {
LocaleProvider, ConfigProvider,
Layout, Layout,
Input, Input,
InputNumber, InputNumber,
@ -50,7 +50,7 @@ import {
} from 'ant-design-vue' } from 'ant-design-vue'
// import VueCropper from 'vue-cropper' // import VueCropper from 'vue-cropper'
Vue.use(LocaleProvider) Vue.use(ConfigProvider)
Vue.use(Layout) Vue.use(Layout)
Vue.use(Input) Vue.use(Input)
Vue.use(InputNumber) Vue.use(InputNumber)

View File

@ -74,6 +74,7 @@ export default {
batchOperate: 'Batch operation', batchOperate: 'Batch operation',
confirmBatchUpdate: 'Confirm batch modification?', confirmBatchUpdate: 'Confirm batch modification?',
batchUpdate: 'Batch update', batchUpdate: 'Batch update',
batchUpdateRelation: 'Create or update relation',
batchUpdateSuccess: 'Batch update successfully', batchUpdateSuccess: 'Batch update successfully',
confirmDelete: 'Confirm deleting ?', confirmDelete: 'Confirm deleting ?',
attribute: 'Attributes', attribute: 'Attributes',

View File

@ -74,6 +74,7 @@ export default {
batchOperate: '批量操作', batchOperate: '批量操作',
confirmBatchUpdate: '确认要批量修改吗 ?', confirmBatchUpdate: '确认要批量修改吗 ?',
batchUpdate: '批量修改', batchUpdate: '批量修改',
batchUpdateRelation: '创建|修改 关系',
batchUpdateSuccess: '批量修改成功', batchUpdateSuccess: '批量修改成功',
confirmDelete: '真的要删除吗 ?', confirmDelete: '真的要删除吗 ?',
attribute: '属性', attribute: '属性',

View File

@ -20,12 +20,20 @@
@click="$refs.create.visible = true; $refs.create.action='update'" @click="$refs.create.visible = true; $refs.create.action='update'"
> >
<span @click="$refs.create.visible = true"> <span @click="$refs.create.visible = true">
<a-icon type="edit" />&nbsp;{{ $t('button.update') }} <a-icon type="edit" />{{ $t('button.update') }}
</span>
</a-menu-item>
<a-menu-item
key="batchUpdateRelation"
@click="$refs.batchUpdateRelation.visible = true"
>
<span @click="$refs.batchUpdateRelation.visible = true">
<a-icon type="link" />{{ $t('ci.batchUpdateRelation') }}
</span> </span>
</a-menu-item> </a-menu-item>
<a-menu-item key="batchDownload" @click="batchDownload"> <a-menu-item key="batchDownload" @click="batchDownload">
<json-excel :fetch="batchDownload" name="cmdb.xls"> <json-excel :fetch="batchDownload" name="cmdb.xls">
<a-icon type="download" />&nbsp;{{ $t('button.download') }} <a-icon type="download" />&nbsp;&nbsp;&nbsp;&nbsp;{{ $t('button.download') }}
</json-excel> </json-excel>
</a-menu-item> </a-menu-item>
<a-menu-item key="batchDelete" @click="batchDelete"> <a-menu-item key="batchDelete" @click="batchDelete">
@ -73,6 +81,7 @@
</s-table> </s-table>
<create-instance-form @refresh="refreshTable" ref="create" @submit="batchUpdate" /> <create-instance-form @refresh="refreshTable" ref="create" @submit="batchUpdate" />
<batch-update-relation :typeId="typeId" ref="batchUpdateRelation" @submit="batchUpdateRelation" />
</a-spin> </a-spin>
</a-card> </a-card>
@ -130,9 +139,11 @@ import JsonExcel from 'vue-json-excel'
import SearchForm from './modules/SearchForm' import SearchForm from './modules/SearchForm'
import CreateInstanceForm from './modules/CreateInstanceForm' import CreateInstanceForm from './modules/CreateInstanceForm'
import BatchUpdateRelation from './modules/BatchUpdateRelation'
import EditableCell from './modules/EditableCell' import EditableCell from './modules/EditableCell'
import CiDetail from './modules/CiDetail' import CiDetail from './modules/CiDetail'
import { searchCI, updateCI, deleteCI } from '@/api/cmdb/ci' import { searchCI, updateCI, deleteCI } from '@/api/cmdb/ci'
import { batchUpdateCIRelation } from '@/api/cmdb/CIRelation'
import { getSubscribeAttributes, subscribeCIType } from '@/api/cmdb/preference' import { getSubscribeAttributes, subscribeCIType } from '@/api/cmdb/preference'
import { notification } from 'ant-design-vue' import { notification } from 'ant-design-vue'
import { getCITypeAttributesByName } from '@/api/cmdb/CITypeAttr' import { getCITypeAttributesByName } from '@/api/cmdb/CITypeAttr'
@ -155,6 +166,7 @@ export default {
JsonExcel, JsonExcel,
SearchForm, SearchForm,
CreateInstanceForm, CreateInstanceForm,
BatchUpdateRelation,
CiDetail CiDetail
}, },
data () { data () {
@ -486,6 +498,38 @@ export default {
} }
}) })
}, },
batchUpdateRelation (values) {
const that = this
this.$confirm({
title: that.$t('tip.warning'),
content: that.$t('ci.confirmBatchUpdate'),
onOk () {
that.loading = true
that.loadTip = that.$t('ci.batchUpdate')
const payload = {}
Object.keys(values).forEach(key => {
if (values[key] || values[key] === 0) {
payload[key] = values[key]
}
})
batchUpdateCIRelation(that.selectedRowKeys, payload).then(() => {
that.loading = false
notification.success({
message: that.$t('ci.batchUpdateSuccess')
})
that.$refs.create.visible = false
that.$refs.table.clearSelected()
}).catch(e => {
console.log(e)
that.loading = false
notification.error({
message: e.response.data.message
})
})
}
})
},
batchDelete () { batchDelete () {
const that = this const that = this
this.$confirm({ this.$confirm({

View File

@ -0,0 +1,80 @@
<template>
<a-drawer
:title="$t('ci.batchUpdateRelation')"
width="50%"
@close="() => { visible = false; $emit('refresh', true) }"
:visible="visible"
:wrapStyle="{ overflow: 'auto' }"
>
<a-form :form="form" :layout="formLayout" @submit="commitUpdateRelation">
<a-button type="primary" @click="commitUpdateRelation">Submit</a-button>
<a-form-item
v-bind="formItemLayout"
:label="item.alias || item.name"
v-for="item in parentCITypes"
:key="item.id"
>
<template v-for="_item in item.attributes">
<a-input
v-decorator="[_item.name, {validateTrigger: ['submit'], rules: []}]"
style="width: 100%"
v-if="_item.id == item.unique_id"
:key="_item.id"
:placeholder="_item.alias || _item.name"
/>
</template>
</a-form-item>
</a-form>
</a-drawer>
</template>
<script>
import { getCITypeParent } from '@/api/cmdb/CITypeRelation'
export default {
props: {
typeId: {
type: Number,
required: true
}
},
data () {
return {
action: '',
form: this.$form.createForm(this),
parentCITypes: [],
visible: false,
formItemLayout: {
labelCol: {
xs: { span: 24 },
sm: { span: 8 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
},
formLayout: 'horizontal'
}
},
created () {
this.getParentCITypes()
},
methods: {
getParentCITypes () {
getCITypeParent(this.typeId).then(res => {
this.parentCITypes = res.parents
})
},
commitUpdateRelation (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
this.$emit('submit', values)
}
})
}
}
}
</script>

View File

@ -2017,10 +2017,10 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies: dependencies:
color-convert "^1.9.0" color-convert "^1.9.0"
ant-design-vue@1.5.0-beta.1: ant-design-vue@1.5.0:
version "1.5.0-beta.1" version "1.5.0"
resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-1.5.0-beta.1.tgz#9396e1bb4435c9bc5dc224e2bd888a000144a8f9" resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-1.5.0.tgz#2e2c5658cf1211be06fbee95a18eee02965e089f"
integrity sha512-Fv5vxO+qHakbjsswgZ70/bjiZK4FphdFxv31VjBJKfhA5Ud7K6sNqC0BVpYS8nL0BwxGCVG8I30efNdU3VifnA== integrity sha512-12+mTowYNZZhsXFR848BZRWGtZrWGayJx9j8Dv3gpgPBU4Abi86tz0hUSbnp8RXL6wb7xNcE9JoawNeE9Y83+Q==
dependencies: dependencies:
"@ant-design/icons" "^2.1.1" "@ant-design/icons" "^2.1.1"
"@ant-design/icons-vue" "^2.0.0" "@ant-design/icons-vue" "^2.0.0"