feat(ui): update ci type

This commit is contained in:
songlh 2024-07-31 16:00:40 +08:00
parent c0c84de600
commit b9c701bfb0
21 changed files with 1865 additions and 748 deletions

View File

@ -3,3 +3,4 @@ VUE_APP_PREVIEW=false
VUE_APP_API_BASE_URL=http://127.0.0.1:5000/api VUE_APP_API_BASE_URL=http://127.0.0.1:5000/api
VUE_APP_BUILD_PACKAGES="ticket,calendar,acl" VUE_APP_BUILD_PACKAGES="ticket,calendar,acl"
VUE_APP_IS_OUTER=true VUE_APP_IS_OUTER=true
VUE_APP_IS_OPEN_SOURCE=true

View File

@ -69,6 +69,8 @@ Vue.prototype.$httpError = function (err, describe) {
window.$message = Vue.prototype.$message window.$message = Vue.prototype.$message
Vue.prototype.isOpenSource = process.env.VUE_APP_IS_OPEN_SOURCE === 'true'
Vue.use(Antd) Vue.use(Antd)
Vue.use(Viser) Vue.use(Viser)

View File

@ -1,232 +1,257 @@
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
/** /**
* 获取 所有的 ci_types * 获取 所有的 ci_types
* @param parameter * @param parameter
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCITypes(parameter) { export function getCITypes(parameter) {
return axios({ return axios({
url: '/v0.1/ci_types', url: '/v0.1/ci_types',
method: 'GET', method: 'GET',
params: parameter params: parameter
}) })
} }
/** /**
* 获取 某个 ci_types * 获取 某个 ci_types
* @param CITypeName * @param CITypeName
* @param parameter * @param parameter
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCIType(CITypeName, parameter) { export function getCIType(CITypeName, parameter) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeName}`, url: `/v0.1/ci_types/${CITypeName}`,
method: 'GET', method: 'GET',
params: parameter params: parameter
}) })
} }
/** /**
* 创建 ci_type * 创建 ci_type
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function createCIType(data) { export function createCIType(data) {
return axios({ return axios({
url: '/v0.1/ci_types', url: '/v0.1/ci_types',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
/** /**
* 更新 ci_type * 更新 ci_type
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function updateCIType(CITypeId, data) { export function updateCIType(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}`, url: `/v0.1/ci_types/${CITypeId}`,
method: 'PUT', method: 'PUT',
data: data data: data
}) })
} }
/** /**
* 删除 ci_type * 删除 ci_type
* @param CITypeId * @param CITypeId
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function deleteCIType(CITypeId) { export function deleteCIType(CITypeId) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}`, url: `/v0.1/ci_types/${CITypeId}`,
method: 'DELETE' method: 'DELETE'
}) })
} }
/** /**
* 获取 某个 ci_type 的分组 * 获取 某个 ci_type 的分组
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCITypeGroupById(CITypeId, data) { export function getCITypeGroupById(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`, url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'GET', method: 'GET',
params: data params: data
}) })
} }
/** /**
* 保存 某个 ci_type 的分组 * 保存 某个 ci_type 的分组
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function createCITypeGroupById(CITypeId, data) { export function createCITypeGroupById(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`, url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
/** /**
* 修改 某个 ci_type 的分组 * 修改 某个 ci_type 的分组
* @param groupId * @param groupId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function updateCITypeGroupById(groupId, data) { export function updateCITypeGroupById(groupId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`, url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'PUT', method: 'PUT',
data: data data: data
}) })
} }
/** /**
* 删除 某个 ci_type 的分组 * 删除 某个 ci_type 的分组
* @param groupId * @param groupId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function deleteCITypeGroupById(groupId, data) { export function deleteCITypeGroupById(groupId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`, url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'delete', method: 'delete',
data: data data: data
}) })
} }
export function getUniqueConstraintList(type_id) { /**
return axios({ * 获取级联属性配置
url: `/v0.1/ci_types/${type_id}/unique_constraint`, * @param {*} typeId
method: 'get', * @returns
}) */
} export function getCITypeCascadeAttributes(typeId) {
return axios({
export function addUniqueConstraint(type_id, data) { url: `/v0.1/cascade_attributes/ci_types/${typeId}`,
return axios({ method: 'get'
url: `/v0.1/ci_types/${type_id}/unique_constraint`, })
method: 'post', }
data: data
}) /**
} * 获取级联属性数据
* @param {*} typeId
export function updateUniqueConstraint(type_id, id, data) { * @returns
return axios({ */
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`, export function postCITypeCascadeAttributesValues(attrId, data) {
method: 'put', return axios({
data: data url: `/v0.1/cascade_attributes/${attrId}/values`,
}) method: 'post',
} data
})
export function deleteUniqueConstraint(type_id, id) { }
return axios({
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`, export function getUniqueConstraintList(type_id) {
method: 'delete', return axios({
}) url: `/v0.1/ci_types/${type_id}/unique_constraint`,
} method: 'get',
})
export function getTriggerList(type_id) { }
return axios({
url: `/v0.1/ci_types/${type_id}/triggers`, export function addUniqueConstraint(type_id, data) {
method: 'get', return axios({
}) url: `/v0.1/ci_types/${type_id}/unique_constraint`,
} method: 'post',
data: data
export function addTrigger(type_id, data) { })
return axios({ }
url: `/v0.1/ci_types/${type_id}/triggers`,
method: 'post', export function updateUniqueConstraint(type_id, id, data) {
data: data return axios({
}) url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
} method: 'put',
data: data
export function updateTrigger(type_id, id, data) { })
return axios({ }
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
method: 'put', export function deleteUniqueConstraint(type_id, id) {
data: data return axios({
}) url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
} method: 'delete',
})
export function deleteTrigger(type_id, id) { }
return axios({
url: `/v0.1/ci_types/${type_id}/triggers/${id}`, export function getTriggerList(type_id) {
method: 'delete', return axios({
}) url: `/v0.1/ci_types/${type_id}/triggers`,
} method: 'get',
})
// CMDB的模型和实例的授权接口 }
export function grantCiType(type_id, rid, data) {
return axios({ export function addTrigger(type_id, data) {
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`, return axios({
method: 'post', url: `/v0.1/ci_types/${type_id}/triggers`,
data method: 'post',
}) data: data
} })
// CMDB的模型和实例的删除授权接口 }
export function revokeCiType(type_id, rid, data) {
return axios({ export function updateTrigger(type_id, id, data) {
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`, return axios({
method: 'post', url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
data method: 'put',
}) data: data
} })
// CMDB的模型和实例的过滤的权限 }
export function ciTypeFilterPermissions(type_id) {
return axios({ export function deleteTrigger(type_id, id) {
url: `/v0.1/ci_types/${type_id}/filters/permissions`, return axios({
method: 'get', url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
}) method: 'delete',
} })
}
// parent_ids, child_id
export function postCiTypeInheritance(data) { // CMDB的模型和实例的授权接口
return axios({ export function grantCiType(type_id, rid, data) {
url: `/v0.1/ci_types/inheritance`, return axios({
method: 'post', url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
data method: 'post',
}) data
} })
}
// parent_id, child_id // CMDB的模型和实例的删除授权接口
export function deleteCiTypeInheritance(data) { export function revokeCiType(type_id, rid, data) {
return axios({ return axios({
url: `/v0.1/ci_types/inheritance`, url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
method: 'delete', method: 'post',
data data
}) })
} }
// CMDB的模型和实例的过滤的权限
export function getCITypeIcons() { export function ciTypeFilterPermissions(type_id) {
return axios({ return axios({
url: '/v0.1/ci_types/icons', url: `/v0.1/ci_types/${type_id}/filters/permissions`,
method: 'GET', method: 'get',
}) })
} }
// parent_ids, child_id
export function postCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'post',
data
})
}
// parent_id, child_id
export function deleteCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'delete',
data
})
}
export function getCITypeIcons() {
return axios({
url: '/v0.1/ci_types/icons',
method: 'GET',
})
}

View File

@ -29,7 +29,7 @@
class="category-side-children-item-corporate" class="category-side-children-item-corporate"
v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))" v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))"
> >
{{ $t('cmdb.enterpriseVersionFlag') }}
</span> </span>
</div> </div>
</div> </div>
@ -67,7 +67,7 @@
class="corporate-flag" class="corporate-flag"
v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))" v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))"
> >
<span class="corporate-flag-text"></span> <span class="corporate-flag-text">{{ $t('cmdb.enterpriseVersionFlag') }}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,6 +2,8 @@ const cmdb_en = {
relation: 'Relation', relation: 'Relation',
attribute: 'Attributes', attribute: 'Attributes',
configTable: 'Config Table', configTable: 'Config Table',
enterpriseVersionFlag: 'Pro',
enterpriseVersionTip: 'Enterprise version only',
menu: { menu: {
views: 'Views', views: 'Views',
topologyView: 'Topology Views', topologyView: 'Topology Views',
@ -274,8 +276,12 @@ const cmdb_en = {
attrAlias: 'Attr Alias', attrAlias: 'Attr Alias',
attrCode: 'Attr Code', attrCode: 'Attr Code',
computedAttrTip1: 'Reference attributes follow jinja2 syntax', computedAttrTip1: 'Reference attributes follow jinja2 syntax',
computedAttrTip2: `Multi-valued attributes (lists) are rendered with [ ] included by default, if you want to remove it, the reference method is: {{ attr_name | join(,)}}} where commas are separators`, computedAttrTip2: `Multi-valued attributes (lists) are rendered with [ ] included by default, if you want to remove it, the reference method is: """{{ attr_name | join(',') }}""" where commas are separators`,
example: 'Example' example: 'Example',
attrFilterTip: `The third column of values allows you to select attributes of this model to cascade attributes`,
rule: 'Rule',
cascadeAttr: 'Cascade',
cascadeAttrTip: 'Cascading attributes note the order',
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -325,6 +331,7 @@ const cmdb_en = {
sub: 'subscription', sub: 'subscription',
selectBelow: 'Please select below', selectBelow: 'Please select below',
subSuccess: 'Subscription successful', subSuccess: 'Subscription successful',
subFailed: 'Subscription failed, please try again later',
selectMethods: 'Please select a method', selectMethods: 'Please select a method',
noAuthRequest: 'No certification requested yet', noAuthRequest: 'No certification requested yet',
noParamRequest: 'No parameter certification yet', noParamRequest: 'No parameter certification yet',
@ -380,6 +387,8 @@ const cmdb_en = {
yearsAgo: 'years ago', yearsAgo: 'years ago',
just: 'just now', just: 'just now',
searchPlaceholder: 'Please search CIType', searchPlaceholder: 'Please search CIType',
subCITable: 'Data',
subCITree: 'Tree',
}, },
custom_dashboard: { custom_dashboard: {
charts: 'Chart', charts: 'Chart',
@ -639,6 +648,7 @@ if __name__ == "__main__":
rollbackingTips: 'Rollbacking', rollbackingTips: 'Rollbacking',
batchRollbacking: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed', batchRollbacking: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID, password and dynamic attributes do not support', baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID, password and dynamic attributes do not support',
cover: 'Cover',
}, },
serviceTree: { serviceTree: {
remove: 'Remove', remove: 'Remove',

View File

@ -2,6 +2,8 @@ const cmdb_zh = {
relation: '关系', relation: '关系',
attribute: '属性', attribute: '属性',
configTable: '配置表格', configTable: '配置表格',
enterpriseVersionFlag: '企',
enterpriseVersionTip: '仅限企业版',
menu: { menu: {
views: '视图', views: '视图',
topologyView: '拓扑视图', topologyView: '拓扑视图',
@ -274,8 +276,12 @@ const cmdb_zh = {
attrAlias: '属性别名', attrAlias: '属性别名',
attrCode: '属性代码', attrCode: '属性代码',
computedAttrTip1: '引用属性遵循jinja2语法', computedAttrTip1: '引用属性遵循jinja2语法',
computedAttrTip2: `多值属性(列表)默认呈现包括[ ], 如果要去掉, 引用方法为: """{{ attr_name | join(',')}}""" 其中逗号为分隔符`, computedAttrTip2: `多值属性(列表)默认呈现包括[ ], 如果要去掉, 引用方法为: """{{ attr_name | join(',') }}""" 其中逗号为分隔符`,
example: '例如' example: '例如',
attrFilterTip: '第三列值可选择本模型的属性,来实现级联属性的功能',
rule: '规则',
cascadeAttr: '级联',
cascadeAttrTip: '级联属性注意顺序',
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -325,6 +331,7 @@ const cmdb_zh = {
sub: '订阅', sub: '订阅',
selectBelow: '请在下方进行选择', selectBelow: '请在下方进行选择',
subSuccess: '订阅成功', subSuccess: '订阅成功',
subFailed: '订阅失败,请稍后再试',
selectMethods: '请选择方式', selectMethods: '请选择方式',
noAuthRequest: '暂无请求认证', noAuthRequest: '暂无请求认证',
noParamRequest: '暂无参数认证', noParamRequest: '暂无参数认证',
@ -379,6 +386,8 @@ const cmdb_zh = {
yearsAgo: '年前', yearsAgo: '年前',
just: '刚刚', just: '刚刚',
searchPlaceholder: '请搜索模型', searchPlaceholder: '请搜索模型',
subCITable: '数据订阅',
subCITree: '层级订阅',
}, },
custom_dashboard: { custom_dashboard: {
charts: '图表', charts: '图表',
@ -638,6 +647,7 @@ if __name__ == "__main__":
rollbackingTips: '正在批量回滚中', rollbackingTips: '正在批量回滚中',
batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个', batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个',
baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性、动态属性不支持回滚', baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性、动态属性不支持回滚',
cover: '覆盖',
}, },
serviceTree: { serviceTree: {
remove: '移除', remove: '移除',

View File

@ -1,431 +1,504 @@
<template> <template>
<CustomDrawer <CustomDrawer
:title="title + CIType.alias" :title="title + CIType.alias"
width="800" width="800"
@close="handleClose" @close="handleClose"
:maskClosable="false" :maskClosable="false"
:visible="visible" :visible="visible"
wrapClassName="create-instance-form" wrapClassName="create-instance-form"
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
:headerStyle="{ borderBottom: 'none' }" :headerStyle="{ borderBottom: 'none' }"
> >
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">{{ $t('cancel') }}</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button> <a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
</div> </div>
<template v-if="action === 'create'"> <template v-if="action === 'create'">
<template v-for="group in attributesByGroup"> <template v-for="group in attributesByGroup">
<CreateInstanceFormByGroup <CreateInstanceFormByGroup
:ref="`createInstanceFormByGroup_${group.id}`" :ref="`createInstanceFormByGroup_${group.id}`"
:key="group.id || group.name" :key="group.id || group.name"
:group="group" :group="group"
@handleFocusInput="handleFocusInput" :attributeList="attributeList"
:attributeList="attributeList" @handleFocusInput="handleFocusInput"
/> />
</template> </template>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
$t('cmdb.menu.citypeRelation') $t('cmdb.menu.citypeRelation')
}}</a-divider> }}</a-divider>
<a-form> <a-form>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col :span="12" v-for="item in parentsType" :key="item.id"> <a-col :span="12" v-for="item in parentsType" :key="item.id">
<a-form-item :label="item.alias || item.name" :colon="false"> <a-form-item :label="item.alias || item.name" :colon="false">
<a-input-group compact style="width: 100%"> <a-input-group compact style="width: 100%">
<a-select v-model="parentsForm[item.name].attr"> <a-select v-model="parentsForm[item.name].attr">
<a-select-option <a-select-option
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
v-for="attr in item.attributes" v-for="attr in item.attributes"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input <a-input
:placeholder="$t('cmdb.ci.tips1')" :placeholder="$t('cmdb.ci.tips1')"
v-model="parentsForm[item.name].value" v-model="parentsForm[item.name].value"
style="width: 50%" style="width: 50%"
/> />
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</template> </template>
</template> </template>
<template v-if="action === 'update'"> <template v-if="action === 'update'">
<a-form :form="form"> <a-form :form="form">
<p>{{ $t('cmdb.ci.tips2') }}</p> <p>{{ $t('cmdb.ci.tips2') }}</p>
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name"> <a-row :gutter="8" v-for="list in batchUpdateLists" :key="list.name">
<a-col :span="11"> <a-col :span="6">
<a-form-item> <a-form-item>
<el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')"> <el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
<el-option <el-option
v-for="attr in attributeList" v-for="attr in attributeList"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1" :disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
:label="attr.alias || attr.name" :label="attr.alias || attr.name"
> >
</el-option> </el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="11"> <a-col v-if="showListOperation(list.name)" :span="3">
<a-form-item> <a-form-item>
<a-select <el-select size="small" filterable v-model="list.operation" :placeholder="$t('placeholder2')">
:style="{ width: '100%' }" <el-option
v-decorator="[list.name, { rules: [{ required: false }] }]" v-for="(option) in listOperationOptions"
:placeholder="$t('placeholder2')" :key="option.value"
v-if="getFieldType(list.name).split('%%')[0] === 'select'" :value="option.value"
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'" :label="$t(option.label)"
showSearch >
allowClear </el-option>
> </el-select>
<a-select-option </a-form-item>
:value="choice[0]" </a-col>
:key="'New_' + choice + choice_idx" <a-col :span="showListOperation(list.name) ? 10 : 13">
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)" <a-form-item>
> <a-select
<span :style="choice[1] ? choice[1].style || {} : {}"> :style="{ width: '100%' }"
<ops-icon v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
:style="{ color: choice[1].icon.color }" :placeholder="$t('placeholder2')"
v-if="choice[1] && choice[1].icon && choice[1].icon.name" v-if="getFieldType(list.name).split('%%')[0] === 'select'"
:type="choice[1].icon.name" :mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
/> showSearch
{{ choice[0] }} allowClear
</span> >
</a-select-option> <a-select-option
</a-select> :value="choice[0]"
<a-input-number :key="'New_' + choice + choice_idx"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
style="width: 100%" >
v-if="getFieldType(list.name) === 'input_number'" <span :style="choice[1] ? choice[1].style || {} : {}">
/> <ops-icon
<a-date-picker :style="{ color: choice[1].icon.color }"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-if="choice[1] && choice[1].icon && choice[1].icon.name"
style="width: 100%" :type="choice[1].icon.name"
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" />
:valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" {{ choice[0] }}
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'" </span>
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }" </a-select-option>
/> </a-select>
<a-input <a-input-number
v-if="getFieldType(list.name) === 'input'" v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
@focus="(e) => handleFocusInput(e, list)" style="width: 100%"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-if="getFieldType(list.name) === 'input_number'"
/> />
</a-form-item> <a-date-picker
</a-col> v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
<a-col :span="2"> style="width: 100%"
<a-form-item> :format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)"> :valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
<a-icon type="delete" /> v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
</a> :showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
</a-form-item> />
</a-col> <a-input
</a-row> v-if="getFieldType(list.name) === 'input'"
<a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button> @focus="(e) => handleFocusInput(e, list)"
</a-form> v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
</template> />
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> </a-form-item>
</CustomDrawer> </a-col>
</template> <a-col :span="2">
<a-form-item>
<script> <a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
import _ from 'lodash' <a-icon type="delete" />
import moment from 'moment' </a>
import { Select, Option } from 'element-ui' </a-form-item>
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType' </a-col>
import { addCI } from '@/modules/cmdb/api/ci' </a-row>
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' <a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
import { valueTypeMap } from '../../../utils/const' </a-form>
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue' </template>
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation' <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</CustomDrawer>
export default { </template>
name: 'CreateInstanceForm',
components: { <script>
ElSelect: Select, import _ from 'lodash'
ElOption: Option, import moment from 'moment'
JsonEditor, import { Select, Option } from 'element-ui'
CreateInstanceFormByGroup, import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
}, import { addCI } from '@/modules/cmdb/api/ci'
props: { import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
typeIdFromRelation: { import { valueTypeMap } from '../../../utils/const'
type: Number, import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
default: 0, import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
},
}, export default {
data() { name: 'CreateInstanceForm',
return { components: {
action: '', ElSelect: Select,
form: this.$form.createForm(this), ElOption: Option,
visible: false, JsonEditor,
attributeList: [], CreateInstanceFormByGroup,
},
CIType: {}, props: {
typeIdFromRelation: {
batchUpdateLists: [], type: Number,
editAttr: null, default: 0,
attributesByGroup: [], },
parentsType: [], },
parentsForm: {}, data() {
canEdit: {}, return {
} action: '',
}, form: this.$form.createForm(this),
computed: { visible: false,
title() { attributeList: [],
return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
}, CIType: {},
typeId() {
if (this.typeIdFromRelation) { batchUpdateLists: [],
return this.typeIdFromRelation editAttr: null,
} attributesByGroup: [],
return this.$router.currentRoute.meta.typeId parentsType: [],
}, parentsForm: {},
valueTypeMap() { canEdit: {},
return valueTypeMap() listOperationOptions: [
}, {
}, value: 'cover',
provide() { label: 'cmdb.ci.cover'
return { },
getFieldType: this.getFieldType, {
} value: 'add',
}, label: 'add'
inject: ['attrList'], },
methods: { {
moment, value: 'delete',
async getCIType() { label: 'delete'
await getCIType(this.typeId).then((res) => { }
this.CIType = res.ci_types[0] ]
}) }
}, },
async getAttributeList() { computed: {
const _attrList = this.attrList() title() {
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required) return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
await getCITypeGroupById(this.typeId).then((res1) => { },
const _attributesByGroup = res1.map((g) => { typeId() {
g.attributes = g.attributes.filter((attr) => !attr.is_computed) if (this.typeIdFromRelation) {
return g return this.typeIdFromRelation
}) }
const attrHasGroupIds = [] return this.$router.currentRoute.meta.typeId
res1.forEach((g) => { },
const id = g.attributes.map((attr) => attr.id) valueTypeMap() {
attrHasGroupIds.push(...id) return valueTypeMap()
}) },
const otherGroupAttr = this.attributeList.filter( },
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed provide() {
) return {
if (otherGroupAttr.length) { getFieldType: this.getFieldType,
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr }) }
} },
console.log(otherGroupAttr, _attributesByGroup) inject: ['attrList'],
this.attributesByGroup = _attributesByGroup methods: {
}) moment,
}, async getCIType() {
createInstance() { await getCIType(this.typeId).then((res) => {
const _this = this this.CIType = res.ci_types[0]
if (_this.action === 'update') { })
this.form.validateFields((err, values) => { },
if (err) { async getAttributeList() {
return const _attrList = this.attrList()
} this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
Object.keys(values).forEach((k) => { await getCITypeGroupById(this.typeId).then((res1) => {
const _tempFind = this.attributeList.find((item) => item.name === k) const _attributesByGroup = res1.map((g) => {
if ( g.attributes = g.attributes.filter((attr) => !attr.is_computed)
_tempFind.value_type === '3' && return g
values[k] && })
Object.prototype.toString.call(values[k]) === '[object Object]' const attrHasGroupIds = []
) { res1.forEach((g) => {
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') const id = g.attributes.map((attr) => attr.id)
} attrHasGroupIds.push(...id)
if ( })
_tempFind.value_type === '4' && const otherGroupAttr = this.attributeList.filter(
values[k] && (attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
Object.prototype.toString.call(values[k]) === '[object Object]' )
) { if (otherGroupAttr.length) {
values[k] = values[k].format('YYYY-MM-DD') _attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
} }
if (_tempFind.value_type === '6') { console.log(otherGroupAttr, _attributesByGroup)
values[k] = values[k] ? JSON.parse(values[k]) : undefined this.attributesByGroup = _attributesByGroup
} })
}) },
createInstance() {
_this.$emit('submit', values) const _this = this
}) if (_this.action === 'update') {
} else { this.form.validateFields({ force: true }, (err, values) => {
let values = {} if (err) {
for (let i = 0; i < this.attributesByGroup.length; i++) { return
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData() }
if (data === 'error') { Object.keys(values).forEach((k) => {
return const _tempFind = this.attributeList.find((item) => item.name === k)
} if (
values = { ...values, ...data } _tempFind.value_type === '3' &&
} values[k] &&
Object.prototype.toString.call(values[k]) === '[object Object]'
Object.keys(values).forEach((k) => { ) {
const _tempFind = this.attributeList.find((item) => item.name === k) values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
if ( }
_tempFind.value_type === '3' && if (
values[k] && _tempFind.value_type === '4' &&
Object.prototype.toString.call(values[k]) === '[object Object]' values[k] &&
) { Object.prototype.toString.call(values[k]) === '[object Object]'
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') ) {
} values[k] = values[k].format('YYYY-MM-DD')
if ( }
_tempFind.value_type === '4' && if (_tempFind.value_type === '6') {
values[k] && values[k] = values[k] ? JSON.parse(values[k]) : undefined
Object.prototype.toString.call(values[k]) === '[object Object]' }
) {
values[k] = values[k].format('YYYY-MM-DD') if (_tempFind.is_list) {
} const operation = this.batchUpdateLists?.find((item) => item.name === k)?.operation || 'cover'
if (_tempFind.value_type === '6') { switch (operation) {
values[k] = values[k] ? JSON.parse(values[k]) : undefined case 'add':
} case 'delete':
}) values[k] = {
values.ci_type = _this.typeId op: operation,
console.log(this.parentsForm) v: values[k]
Object.keys(this.parentsForm).forEach((type) => { }
if (this.parentsForm[type].value) { break
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value default:
} break
}) }
addCI(values).then((res) => { }
_this.$message.success(this.$t('addSuccess')) })
_this.visible = false
_this.$emit('reload', { ci_id: res.ci_id }) _this.$emit('submit', values)
}) })
} } else {
let values = {}
// this.form.validateFields((err, values) => { for (let i = 0; i < this.attributesByGroup.length; i++) {
// if (err) { const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
// _this.$message.error('字段填写不符合要求!') if (data === 'error') {
// return return
// } }
// Object.keys(values).forEach((k) => { values = { ...values, ...data }
// if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) { }
// values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
// } Object.keys(values).forEach((k) => {
// const _tempFind = this.attributeList.find((item) => item.name === k) const _tempFind = this.attributeList.find((item) => item.name === k)
// if (_tempFind.value_type === '6') { if (
// values[k] = values[k] ? JSON.parse(values[k]) : undefined _tempFind.value_type === '3' &&
// } values[k] &&
// }) Object.prototype.toString.call(values[k]) === '[object Object]'
) {
// if (_this.action === 'update') { values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
// _this.$emit('submit', values) }
// return if (
// } _tempFind.value_type === '4' &&
// values.ci_type = _this.typeId values[k] &&
// console.log(values) Object.prototype.toString.call(values[k]) === '[object Object]'
// this.attributesByGroup.forEach((group) => { ) {
// this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData() values[k] = values[k].format('YYYY-MM-DD')
// }) }
// console.log(1111) if (_tempFind.value_type === '6') {
// // addCI(values).then((res) => { values[k] = values[k] ? JSON.parse(values[k]) : undefined
// // _this.$message.success('新增成功!') }
// // _this.visible = false })
// // _this.$emit('reload') values.ci_type = _this.typeId
// // }) console.log(this.parentsForm)
// }) Object.keys(this.parentsForm).forEach((type) => {
}, if (this.parentsForm[type].value) {
handleClose() { values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
this.visible = false }
}, })
handleOpen(visible, action) { addCI(values).then((res) => {
this.visible = visible _this.$message.success(this.$t('addSuccess'))
this.action = action _this.visible = false
this.$nextTick(() => { _this.$emit('reload', { ci_id: res.ci_id })
this.form.resetFields() })
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => { }
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
}) // this.form.validateFields((err, values) => {
if (action === 'create') { // if (err) {
getCITypeParent(this.typeId).then(async (res) => { // _this.$message.error('字段填写不符合要求!')
for (let i = 0; i < res.parents.length; i++) { // return
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => { // }
this.canEdit = { // Object.keys(values).forEach((k) => {
..._.cloneDeep(this.canEdit), // if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) {
[res.parents[i].id]: p_res.result, // values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
} // }
}) // const _tempFind = this.attributeList.find((item) => item.name === k)
} // if (_tempFind.value_type === '6') {
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id]) // values[k] = values[k] ? JSON.parse(values[k]) : undefined
const _parentsForm = {} // }
res.parents.forEach((item) => { // })
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
_parentsForm[item.name] = { attr: _find.name, value: '' } // if (_this.action === 'update') {
}) // _this.$emit('submit', values)
this.parentsForm = _parentsForm // return
}) // }
} // values.ci_type = _this.typeId
}) // console.log(values)
}, // this.attributesByGroup.forEach((group) => {
getFieldType(name) { // this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData()
const _find = this.attributeList.find((item) => item.name === name) // })
if (_find) { // console.log(1111)
if (_find.is_choice) { // // addCI(values).then((res) => {
if (_find.is_list) { // // _this.$message.success('新增成功!')
return 'select%%multiple' // // _this.visible = false
} // // _this.$emit('reload')
return 'select' // // })
} else if ((_find.value_type === '0' || _find.value_type === '1') && !_find.is_list) { // })
return 'input_number' },
} else if (_find.value_type === '4' || _find.value_type === '3') { handleClose() {
return _find.value_type this.visible = false
} else { },
return 'input' handleOpen(visible, action) {
} this.visible = visible
} this.action = action
return 'input' this.$nextTick(() => {
}, this.form.resetFields()
getSelectFieldOptions(name) { Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
const _find = this.attributeList.find((item) => item.name === name) this.batchUpdateLists = [{
if (_find) { name: this.attributeList?.[0]?.name || undefined,
return _find.choice_value operation: 'cover'
} }]
return [] })
}, if (action === 'create') {
handleAdd() { getCITypeParent(this.typeId).then(async (res) => {
this.batchUpdateLists.push({ name: undefined }) for (let i = 0; i < res.parents.length; i++) {
}, await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
handleDelete(name) { this.canEdit = {
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name) ..._.cloneDeep(this.canEdit),
if (_idx > -1) { [res.parents[i].id]: p_res.result,
this.batchUpdateLists.splice(_idx, 1) }
} })
}, }
// filterOption(input, option) { this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 const _parentsForm = {}
// }, res.parents.forEach((item) => {
handleFocusInput(e, attr) { const _find = item.attributes.find((attr) => attr.id === item.unique_id)
console.log(attr) _parentsForm[item.name] = { attr: _find.name, value: '' }
const _tempFind = this.attributeList.find((item) => item.name === attr.name) })
if (_tempFind.value_type === '6') { this.parentsForm = _parentsForm
this.editAttr = attr })
e.srcElement.blur() }
const jsonData = this.form.getFieldValue(attr.name) })
this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {}) },
} else { getFieldType(name) {
this.editAttr = null const _find = this.attributeList.find((item) => item.name === name)
} if (_find) {
}, if (_find.is_choice) {
jsonEditorOk(jsonData) { if (_find.is_list) {
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) }) return 'select%%multiple'
}, }
}, return 'select'
} } else if ((_find.value_type === '0' || _find.value_type === '1') && !_find.is_list) {
</script> return 'input_number'
<style lang="less"> } else if (_find.value_type === '4' || _find.value_type === '3') {
.create-instance-form { return _find.value_type
.ant-form-item { } else {
margin-bottom: 5px; return 'input'
} }
.ant-drawer-body { }
overflow-y: auto; return 'input'
max-height: calc(100vh - 110px); },
} getSelectFieldOptions(name) {
} const _find = this.attributeList.find((item) => item.name === name)
</style> if (_find) {
return _find.choice_value
}
return []
},
handleAdd() {
this.batchUpdateLists.push({
name: undefined,
operation: 'cover'
})
},
handleDelete(name) {
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
if (_idx > -1) {
this.batchUpdateLists.splice(_idx, 1)
}
},
// filterOption(input, option) {
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
// },
handleFocusInput(e, attr) {
console.log(attr)
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
if (_tempFind.value_type === '6') {
this.editAttr = attr
e.srcElement.blur()
const jsonData = this.form.getFieldValue(attr.name)
this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
} else {
this.editAttr = null
}
},
jsonEditorOk(jsonData) {
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
},
showListOperation(name) {
if (!name) {
return false
}
const attr = this.attributeList.find((attr) => attr.name === name)
return attr && attr.is_list
},
getDecoratorRules(data) {
const { name, operation } = data
const isList = this.showListOperation(name)
const rules = [
{ required: false }
]
if (isList && ['delete', 'add'].includes(operation)) {
rules[0] = {
required: true,
message: this.$t('placeholder1')
}
}
return rules
}
},
}
</script>
<style lang="less">
.create-instance-form {
.ant-form-item {
margin-bottom: 5px;
}
.ant-drawer-body {
overflow-y: auto;
max-height: calc(100vh - 110px);
}
}
</style>

View File

@ -92,7 +92,7 @@ export default {
} }
attr.groupId = -1 attr.groupId = -1
attr.groupName = '其他' attr.groupName = this.$t('other')
attr.code = `{{ ${attr.name} }}` attr.code = `{{ ${attr.name} }}`
attr.typeText = typeMap?.[attr.value_type] ?? '' attr.typeText = typeMap?.[attr.value_type] ?? ''
}) })

View File

@ -454,10 +454,15 @@ export default {
query_expr: _findADT.query_expr || '', query_expr: _findADT.query_expr || '',
enabled: _findADT?.enabled ?? true, enabled: _findADT?.enabled ?? true,
} }
const allMachineIndex = this.agentTypeRadioList.findIndex((item) => item.value === 'all')
if (_findADT.query_expr) { if (_findADT.query_expr) {
this.agent_type = 'query_expr' this.agent_type = 'query_expr'
} else if (_findADT.agent_id) { } else if (_findADT.agent_id) {
this.agent_type = _findADT.agent_id === '0x0000' ? 'master' : 'agent_id' this.agent_type = _findADT.agent_id === '0x0000' ? 'master' : 'agent_id'
} else if (_findADT.agent_id === '' && allMachineIndex !== -1) {
this.agent_type = 'all'
} else { } else {
this.agent_type = this.agentTypeRadioList[0].value this.agent_type = this.agentTypeRadioList[0].value
} }

View File

@ -345,6 +345,7 @@
:canDefineScript="canDefineScript" :canDefineScript="canDefineScript"
ref="preValueArea" ref="preValueArea"
:disabled="isShowComputedArea" :disabled="isShowComputedArea"
:CITypeId="CITypeId"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>

View File

@ -18,19 +18,19 @@
<a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button> <a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
<div> <div>
<a-tooltip <a-tooltip
v-for="type in Object.keys(valueTypeMap)" v-for="typeKey in Object.keys(valueTypeMap)"
:key="type" :key="typeKey"
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[type] })" :title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[typeKey] })"
> >
<span <span
@click="handleFilterType(type)" @click="handleFilterType(typeKey)"
:class="{ :class="{
'ci-types-attributes-filter': true, 'ci-types-attributes-filter': true,
'ci-types-attributes-filter-selected': attrTypeFilter.includes(type), 'ci-types-attributes-filter-selected': attrTypeFilter.includes(typeKey),
}" }"
> >
<ops-icon :type="getPropertyIcon({ value_type: type })" /> <ops-icon :type="getPropertyIcon({ value_type: typeKey })" />
{{ valueTypeMap[type] }} {{ valueTypeMap[typeKey] }}
</span> </span>
</a-tooltip> </a-tooltip>
</div> </div>

View File

@ -333,7 +333,12 @@
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')">
<PreValueArea ref="preValueArea" :canDefineScript="canDefineScript" :disabled="isShowComputedArea" /> <PreValueArea
ref="preValueArea"
:canDefineScript="canDefineScript"
:disabled="isShowComputedArea"
:CITypeId="CITypeId"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
@ -402,6 +407,10 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
CITypeId: {
type: Number,
default: null
}
}, },
data() { data() {
return { return {

View File

@ -32,6 +32,8 @@ import TriggerTable from './triggerTable.vue'
import ADTab from './adTab.vue' import ADTab from './adTab.vue'
import GrantComp from '../../components/cmdbGrant/grantComp.vue' import GrantComp from '../../components/cmdbGrant/grantComp.vue'
const ACTIVE_KEY_STORAGE_KEY = 'ops_model_config_tab_key'
export default { export default {
name: 'CITypeDetail', name: 'CITypeDetail',
components: { components: {
@ -53,11 +55,24 @@ export default {
}, },
data() { data() {
return { return {
activeKey: '1', activeKey: localStorage.getItem(ACTIVE_KEY_STORAGE_KEY) || '1',
} }
}, },
beforeCreate() {}, beforeCreate() {},
mounted() {}, mounted() {
this.$nextTick(() => {
switch (this.activeKey) {
case '6':
this.$refs.triggerTable.getTableData()
break
case '5':
this.$refs.reconciliationTable.getTableData()
break
default:
break
}
})
},
computed: { computed: {
...mapState({ ...mapState({
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
@ -66,15 +81,23 @@ export default {
methods: { methods: {
changeTab(activeKey) { changeTab(activeKey) {
this.activeKey = activeKey this.activeKey = activeKey
localStorage.setItem(ACTIVE_KEY_STORAGE_KEY, activeKey)
this.$nextTick(() => { this.$nextTick(() => {
if (activeKey === '1') { switch (activeKey) {
this.$refs.attributesTable.getCITypeGroupData() case '1':
} this.$refs.attributesTable.getCITypeGroupData()
if (activeKey === '5') { break
this.$refs.triggerTable.getTableData() case '6':
this.$refs.triggerTable.getTableData()
break
case '5':
this.$refs.reconciliationTable.getTableData()
break
default:
break
} }
}) })
}, }
}, },
} }
</script> </script>

View File

@ -1,17 +1,16 @@
<template> <template>
<a-tabs v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }"> <a-tabs v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }" @change="handleTabsChange">
<a-tab-pane key="expr" :disabled="!canDefineComputed"> <a-tab-pane key="expr" :disabled="!canDefineComputed">
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.expr') }}</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.expr') }}</span>
<a-textarea v-model="compute_expr" :placeholder="`{{a}}+{{b}}`" :rows="2" :disabled="!canDefineComputed" /> <a-textarea v-model="compute_expr" :placeholder="`{{a}}+{{b}}`" :rows="2" :disabled="!canDefineComputed" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="script" :disabled="!canDefineComputed"> <a-tab-pane key="script" :disabled="!canDefineComputed">
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
<codemirror <CustomCodeMirror
style="z-index: 9999" codeMirrorId="cmdb-computed-attr"
:options="cmOptions" ref="codemirror"
v-model="compute_script" @changeCodeContent="onCodeChange"
@input="onCodeChange" ></CustomCodeMirror>
></codemirror>
</a-tab-pane> </a-tab-pane>
<template slot="tabBarExtraContent"> <template slot="tabBarExtraContent">
<a-button size="small" @click="showAllPropDrawer"> <a-button size="small" @click="showAllPropDrawer">
@ -33,7 +32,8 @@
<script> <script>
import AllAttrDrawer from './allAttrDrawer.vue' import AllAttrDrawer from './allAttrDrawer.vue'
import { codemirror } from 'vue-codemirror'
import CustomCodeMirror from '@/components/CustomCodeMirror'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
@ -41,7 +41,7 @@ require('codemirror/mode/python/python.js')
export default { export default {
name: 'ComputedArea', name: 'ComputedArea',
components: { components: {
codemirror, CustomCodeMirror,
AllAttrDrawer AllAttrDrawer
}, },
props: { props: {
@ -59,33 +59,6 @@ export default {
activeKey: 'expr', // expr script activeKey: 'expr', // expr script
compute_expr: '', compute_expr: '',
compute_script: 'def computed(): \n return', compute_script: 'def computed(): \n return',
cmOptions: {
lineNumbers: true,
mode: 'python',
height: '200px',
theme: 'monokai',
tabSize: 4,
indentUnit: 4,
lineWrapping: false,
readOnly: !this.canDefineComputed,
extraKeys: {
Tab: (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('add')
} else {
cm.replaceSelection(Array(cm.getOption('indentUnit') + 1).join(' '), 'end', '+input')
}
},
'Shift-Tab': (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('subtract')
} else {
const cursor = cm.getCursor()
cm.setCursor({ line: cursor.line, ch: cursor.ch - 4 })
}
},
},
},
} }
}, },
methods: { methods: {
@ -103,6 +76,9 @@ export default {
this.compute_script = compute_script || 'def computed(): \n return' this.compute_script = compute_script || 'def computed(): \n return'
if (compute_script) { if (compute_script) {
this.activeKey = 'script' this.activeKey = 'script'
this.$nextTick(() => {
this.$refs.codemirror.initCodeMirror(this.compute_script)
})
} else { } else {
this.activeKey = 'expr' this.activeKey = 'expr'
} }
@ -122,6 +98,15 @@ export default {
}, },
showAllPropDrawer() { showAllPropDrawer() {
this.$refs.allAttrDrawer.open() this.$refs.allAttrDrawer.open()
},
handleTabsChange(activeKey) {
console.log('handleTabsChange', activeKey)
if (activeKey === 'script') {
this.$nextTick(() => {
this.$refs.codemirror.initCodeMirror(this.compute_script)
})
}
} }
}, },
} }

View File

@ -28,7 +28,7 @@
<a-tabs v-model="activeKey"> <a-tabs v-model="activeKey">
<a-tab-pane key="1" :tab="$t('cmdb.ciType.addAttribute')"> <a-tab-pane key="1" :tab="$t('cmdb.ciType.addAttribute')">
<div :style="{ overflow: 'auto', maxHeight: '480px' }"> <div :style="{ overflow: 'auto', maxHeight: '480px' }">
<create-new-attribute ref="createNewAttribute" :hasFooter="false" @done="handleAddNewAttr" /> <create-new-attribute ref="createNewAttribute" :hasFooter="false" :CITypeId="CITypeId" @done="handleAddNewAttr" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" :tab="$t('cmdb.ciType.existedAttributes')" force-render> <a-tab-pane key="2" :tab="$t('cmdb.ciType.existedAttributes')" force-render>

View File

@ -61,12 +61,12 @@
<a-tab-pane key="choice_other" :disabled="disabled"> <a-tab-pane key="choice_other" :disabled="disabled">
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.choiceOther') }}</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.choiceOther') }}</span>
<a-row :gutter="[24, 24]"> <a-row :gutter="[24, 24]">
<a-col :span="12"> <a-col :span="24">
<a-form-item <a-form-item
:style="{ lineHeight: '24px', marginBottom: '5px' }" :style="{ lineHeight: '24px', marginBottom: '5px' }"
:label="$t('cmdb.ciType.ciType')" :label="$t('cmdb.ciType.ciType')"
:label-col="{ span: 4 }" :label-col="{ span: 3 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 12 }"
> >
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
@ -117,12 +117,12 @@
</treeselect> </treeselect>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length"> <a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item <a-form-item
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
:label="$t('cmdb.ciType.attributes')" :label="$t('cmdb.ciType.attributes')"
:label-col="{ span: 4 }" :label-col="{ span: 3 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 12 }"
> >
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
@ -162,15 +162,17 @@
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
class="pre-value-filter" class="pre-value-filter"
:label="$t('cmdb.ciType.filter')" :label="$t('cmdb.ciType.filter')"
:label-col="{ span: 2 }" :label-col="{ span: 3 }"
:wrapper-col="{ span: 22 }" :wrapper-col="{ span: 19 }"
> >
<FilterComp <AttrFilter
ref="filterComp" ref="attrFilter"
:isDropdown="false" :isDropdown="false"
:canSearchPreferenceAttrList="typeAttrs" :canSearchPreferenceAttrList="typeAttrs"
@setExpFromFilter="setExpFromFilter" :CITypeId="CITypeId"
:expression="filterExp ? `q=${filterExp}` : ''" :expression="filterExp ? `q=${filterExp}` : ''"
:curModelAttrList="curModelAttrList"
@setExpFromFilter="setExpFromFilter"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@ -178,6 +180,42 @@
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="script" :disabled="disabled || !canDefineScript"> <a-tab-pane key="script" :disabled="disabled || !canDefineScript">
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
<a-form-item
:style="{ marginBottom: '5px' }"
:label="$t('cmdb.ciType.cascadeAttr')"
:label-col="{ span: 3 }"
:wrapper-col="{ span: 19 }"
:extra="scriptCodeExtraText"
labelAlign="left"
>
<a-select
mode="multiple"
style="width: 100%"
placeholder="Please select"
optionFilterProp="title"
v-model="cascade_attributes"
>
<a-select-option
v-for="attr in curModelAttrList"
:key="attr.id"
:title="attr.name"
>
{{ attr.name }}
</a-select-option>
</a-select>
</a-form-item>
<div class="script-tip">
<div>1. {{ $t('cmdb.ciType.computedAttrTip1') }}</div>
<div>2. {{ $t('cmdb.ciType.computedAttrTip2') }}</div>
</div>
<a-button size="small" @click="showAllPropDrawer">
{{ $t('cmdb.ciType.viewAllAttr') }}
</a-button>
<AllAttrDrawer ref="allAttrDrawer" />
<CustomCodeMirror <CustomCodeMirror
codeMirrorId="cmdb-pre-value" codeMirrorId="cmdb-pre-value"
ref="codemirror" ref="codemirror"
@ -196,16 +234,18 @@ import { defautValueColor } from '../../utils/const'
import ColorPicker from '../../components/colorPicker/index.vue' import ColorPicker from '../../components/colorPicker/index.vue'
import Webhook from '../../components/webhook' import Webhook from '../../components/webhook'
import { getCITypeGroups } from '../../api/ciTypeGroup' import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr' import { getCITypeCommonAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
import FilterComp from '@/components/CMDBFilterComp' import AttrFilter from './preValueAttr/attrFilter/index.vue'
import AllAttrDrawer from './allAttrDrawer.vue'
import CustomCodeMirror from '@/components/CustomCodeMirror' import CustomCodeMirror from '@/components/CustomCodeMirror'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
require('codemirror/mode/python/python.js') require('codemirror/mode/python/python.js')
export default { export default {
name: 'PreValueArea', name: 'PreValueArea',
components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp, CustomCodeMirror }, components: { draggable, PreValueTag, ColorPicker, Webhook, AttrFilter, CustomCodeMirror, AllAttrDrawer },
props: { props: {
disabled: { disabled: {
type: Boolean, type: Boolean,
@ -215,6 +255,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
CITypeId: {
type: Number,
default: null,
},
}, },
data() { data() {
return { return {
@ -242,6 +286,13 @@ export default {
lineWrapping: true, lineWrapping: true,
readOnly: this.disabled || !this.canDefineScript, readOnly: this.disabled || !this.canDefineScript,
}, },
curModelAttrList: [], // 当前模型属性
cascade_attributes: [] // 级联属性id列表
}
},
computed: {
scriptCodeExtraText() {
return this.$t('cmdb.ciType.cascadeAttrTip') + (this.isOpenSource ? ` (${this.$t('cmdb.enterpriseVersionTip')})` : '')
} }
}, },
watch: { watch: {
@ -276,8 +327,18 @@ export default {
return { ..._.cloneDeep(item) } return { ..._.cloneDeep(item) }
}) })
}) })
this.getCITypeAttributesById()
}, },
methods: { methods: {
async getCITypeAttributesById() {
const res = await getCITypeAttributesById(this.CITypeId)
let curModelAttrList = []
if (res?.attributes?.length) {
curModelAttrList = res.attributes.filter(attr => !attr.is_password)
}
this.curModelAttrList = curModelAttrList
},
addNewValue(newValue, newStyle, newIcon) { addNewValue(newValue, newStyle, newIcon) {
if (newValue) { if (newValue) {
const idx = this.valueList.findIndex((v) => v[0] === newValue) const idx = this.valueList.findIndex((v) => v[0] === newValue)
@ -321,12 +382,13 @@ export default {
choice_web_hook: null, choice_web_hook: null,
choice_other: { choice_other: {
script: this.script, script: this.script,
cascade_attributes: this.cascade_attributes,
}, },
} }
} else { } else {
let choice_other = {} let choice_other = {}
if (this.choice_other.type_ids && this.choice_other.type_ids.length) { if (this.choice_other.type_ids && this.choice_other.type_ids.length) {
this.$refs.filterComp.handleSubmit() this.$refs.attrFilter.handleSubmit()
choice_other = { ...this.choice_other, filter: this.filterExp } choice_other = { ...this.choice_other, filter: this.filterExp }
} }
return { return {
@ -355,9 +417,10 @@ export default {
const { type_ids, attr_id, filter } = choice_other const { type_ids, attr_id, filter } = choice_other
this.choice_other = { type_ids, attr_id } this.choice_other = { type_ids, attr_id }
this.filterExp = filter this.filterExp = filter
this.cascade_attributes = choice_other?.cascade_attributes || []
if (type_ids && type_ids.length) { if (type_ids && type_ids.length) {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.filterComp.visibleChange(true, false) this.$refs.attrFilter.init(true, false)
}) })
} }
} }
@ -390,6 +453,10 @@ export default {
}) })
} }
}, },
showAllPropDrawer() {
this.$refs.allAttrDrawer.open()
},
}, },
} }
</script> </script>
@ -408,6 +475,12 @@ export default {
margin: 5px; margin: 5px;
} }
} }
.script-tip {
font-size: 12px;
line-height: 22px;
color: #a5a9bc;
}
</style> </style>
<style lang="less"> <style lang="less">

View File

@ -0,0 +1,321 @@
<template>
<div>
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
<div v-if="ruleList.length > 1" :style="{ width: '60px', height: rowHeight, position: 'relative' }">
<treeselect
v-if="index !== 0"
class="custom-treeselect"
:style="{ width: '60px', '--custom-height': rowHeight, position: 'absolute', top: '-24px' }"
v-model="item.type"
:multiple="false"
:clearable="false"
searchable
:options="ruleTypeList"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
:disabled="disabled"
>
</treeselect>
</div>
<treeselect
class="custom-treeselect"
:style="{ width: '120px', '--custom-height': rowHeight }"
v-model="item.property"
:multiple="false"
:clearable="false"
searchable
:options="canSearchPreferenceAttrList"
:normalizer="
(node) => {
return {
id: node.name,
label: node.alias || node.name,
children: node.children,
}
}
"
appendToBody
:zIndex="1050"
:disabled="disabled"
>
<div
v-if="node.id !== '$count'"
:title="node.label"
slot="option-label"
slot-scope="{ node }"
class="property-label"
>
<ValueTypeMapIcon :attr="node.raw" />
{{ node.label }}
</div>
<div
v-else
:title="node.label"
slot="option-label"
slot-scope="{ node }"
class="property-label"
:style="{ borderBottom: '1px solid #E4E7ED', marginBottom: '8px' }"
>
<ValueTypeMapIcon :attr="node.raw" />
{{ node.label }}
</div>
<div
class="property-label"
slot="value-label"
slot-scope="{ node }"
>
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
</div>
</treeselect>
<treeselect
class="custom-treeselect"
:style="{ width: '90px', '--custom-height': rowHeight }"
v-model="item.exp"
:multiple="false"
:clearable="false"
searchable
:options="getExpListByProperty(item.property)"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
@select="(value) => handleChangeExp(value, item, index)"
appendToBody
:zIndex="1050"
:disabled="disabled"
>
</treeselect>
<ValueControls
:rule="ruleList[index]"
:attrList="canSearchPreferenceAttrList"
:disabled="disabled"
:curModelAttrList="curModelAttrList"
:rowHeight="rowHeight"
@change="(value) => handleChangeValue(value, index)"
/>
<template v-if="!disabled">
<a-tooltip :title="$t('copy')">
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
</a-tooltip>
<a-tooltip :title="$t('delete')">
<a class="operation" @click="handleDeleteRule(item)"><a-icon type="minus-circle"/></a>
</a-tooltip>
<a-tooltip :title="$t('cmdbFilterComp.addHere')">
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
</a-tooltip>
</template>
</a-space>
<div class="table-filter-add" v-if="!disabled && ruleList.length === 0">
<a @click="handleAddRule">+ {{ $t('new') }}</a>
</div>
<div class="attr-filter-tip">{{ $t('cmdb.ciType.attrFilterTip') }}{{ isOpenSource ? ` (${$t('cmdb.enterpriseVersionTip')})` : '' }}</div>
</div>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import { ruleTypeList, expList, advancedExpList, compareTypeList } from '../constants.js'
import ValueTypeMapIcon from '@/components/CMDBValueTypeMapIcon'
import ValueControls from './valueControls.vue'
export default {
name: 'Expression',
components: {
ValueTypeMapIcon,
ValueControls
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Array,
default: () => [],
},
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
curModelAttrList: {
type: Array,
default: () => []
}
},
data() {
return {
compareTypeList,
rowHeight: '36px'
}
},
computed: {
ruleList: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
return val
},
},
ruleTypeList() {
return ruleTypeList()
},
expList() {
return expList()
},
advancedExpList() {
return advancedExpList()
},
},
methods: {
getExpListByProperty(property) {
if (property === '$count') {
return [
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
{ value: 'compare', label: this.$t('cmdbFilterComp.compare') }
]
}
if (property) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
return [
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
...this.advancedExpList
]
}
}
return [...this.expList, ...this.advancedExpList]
},
isChoiceByProperty(property) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
if (_find) {
return _find.is_choice
}
return false
},
handleAddRule() {
this.ruleList.push({
id: uuidv4(),
type: 'and',
property: this.canSearchPreferenceAttrList[0]?.name,
exp: 'is',
value: null,
})
this.$emit('change', this.ruleList)
},
handleCopyRule(item) {
this.ruleList.push({ ...item, id: uuidv4() })
this.$emit('change', this.ruleList)
},
handleDeleteRule(item) {
const idx = this.ruleList.findIndex((r) => r.id === item.id)
if (idx > -1) {
this.ruleList.splice(idx, 1)
}
this.$emit('change', this.ruleList)
},
handleAddRuleAt(item) {
const idx = this.ruleList.findIndex((r) => r.id === item.id)
if (idx > -1) {
this.ruleList.splice(idx + 1, 0, {
id: uuidv4(),
type: 'and',
property: this.canSearchPreferenceAttrList[0]?.name,
exp: 'is',
value: null,
})
}
this.$emit('change', this.ruleList)
},
getChoiceValueByProperty(property) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
if (_find) {
return _find.choice_value
}
return []
},
handleChangeExp({ value }, item, index) {
const _ruleList = _.cloneDeep(this.ruleList)
if (value === 'range') {
_ruleList[index] = {
..._ruleList[index],
min: '',
max: '',
exp: value,
}
} else if (value === 'compare') {
_ruleList[index] = {
..._ruleList[index],
compareType: '1',
exp: value,
}
} else {
_ruleList[index] = {
..._ruleList[index],
exp: value,
}
}
this.ruleList = _ruleList
this.$emit('change', this.ruleList)
},
handleChangeValue(value, index) {
const _ruleList = _.cloneDeep(this.ruleList)
_ruleList[index] = value
this.$emit('change', _ruleList)
}
},
}
</script>
<style lang="less" scoped>
.input-group {
display: flex;
align-items: center;
width: 150px;
&-range-icon {
margin: 0 8px;
}
input {
height: 36px;
}
}
.property-label {
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden
}
.attr-filter-tip {
color: #86909C;
font-size: 12px;
font-weight: 400;
}
</style>

View File

@ -0,0 +1,211 @@
<template>
<div>
<Expression
v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="false"
:curModelAttrList="curModelAttrList"
/>
</div>
</template>
<script>
import { v4 as uuidv4 } from 'uuid'
import { compareTypeList } from '../constants.js'
import Expression from './expression.vue'
export default {
name: 'AttrFilter',
components: {
Expression
},
props: {
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
expression: {
type: String,
default: '',
},
regQ: {
type: String,
default: '(?<=q=).+(?=&)|(?<=q=).+$',
},
CITypeId: {
type: Number,
default: null,
},
curModelAttrList: {
type: Array,
default: () => []
}
},
data() {
return {
compareTypeList,
visible: false,
ruleList: [],
filterExp: '',
}
},
methods: {
init(open, isInitOne = true) {
// isInitOne 初始化exp为空时ruleList是否默认给一条
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
: null
if (open && exp) {
const expArray = exp.split(',').map((item) => {
let has_not = ''
const key = item.split(':')[0]
const val = item
.split(':')
.slice(1)
.join(':')
let type, property, exp, value, min, max, compareType
if (key.includes('-')) {
type = 'or'
if (key.includes('~')) {
property = key.substring(2)
has_not = '~'
} else {
property = key.substring(1)
}
} else {
type = 'and'
if (key.includes('~')) {
property = key.substring(1)
has_not = '~'
} else {
property = key
}
}
const in_reg = /(?<=\().+(?=\))/g
const range_reg = /(?<=\[).+(?=\])/g
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
if (val === '*') {
exp = has_not + 'value'
value = ''
} else if (in_reg.test(val)) {
exp = has_not + 'in'
value = val.match(in_reg)[0]
} else if (range_reg.test(val)) {
exp = has_not + 'range'
value = val.match(range_reg)[0]
min = value.split('_TO_')[0]
max = value.split('_TO_')[1]
} else if (compare_reg.test(val)) {
exp = has_not + 'compare'
value = val.match(compare_reg)[0]
const _compareType = val.substring(0, val.match(compare_reg)['index'])
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
compareType = compareTypeList[idx].value
} else if (!val.includes('*')) {
exp = has_not + 'is'
value = val
} else {
const resList = [
['contain', /(?<=\*).*(?=\*)/g],
['end_with', /(?<=\*).+/g],
['start_with', /.+(?=\*)/g],
]
for (let i = 0; i < 3; i++) {
const reg = resList[i]
if (reg[1].test(val)) {
exp = has_not + reg[0]
value = val.match(reg[1])[0]
break
}
}
}
return {
id: uuidv4(),
type,
property,
exp,
value,
min,
max,
compareType,
}
})
this.ruleList = [...expArray]
} else if (open) {
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
this.ruleList = isInitOne
? [
{
id: uuidv4(),
type: 'and',
property:
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
? _canSearchPreferenceAttrList[0].name
: undefined,
exp: 'is',
value: null,
},
]
: []
}
},
handleSubmit() {
if (this.ruleList && this.ruleList.length) {
this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and
this.filterExp = ''
const expList = this.ruleList.map((rule) => {
let singleRuleExp = ''
let _exp = rule.exp
if (rule.type === 'or') {
singleRuleExp += '-'
}
if (rule.exp.includes('~')) {
singleRuleExp += '~'
_exp = rule.exp.split('~')[1]
}
singleRuleExp += `${rule.property}:`
if (_exp === 'is') {
singleRuleExp += `${rule.value ?? ''}`
}
if (_exp === 'contain') {
singleRuleExp += `*${rule.value ?? ''}*`
}
if (_exp === 'start_with') {
singleRuleExp += `${rule.value ?? ''}*`
}
if (_exp === 'end_with') {
singleRuleExp += `*${rule.value ?? ''}`
}
if (_exp === 'value') {
singleRuleExp += `*`
}
if (_exp === 'in') {
singleRuleExp += `(${rule.value ?? ''})`
}
if (_exp === 'range') {
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
}
if (_exp === 'compare') {
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
}
return singleRuleExp
})
this.filterExp = expList.join(',')
this.$emit('setExpFromFilter', this.filterExp)
} else {
this.$emit('setExpFromFilter', '')
}
this.visible = false
},
},
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,273 @@
<template>
<div>
<div class="control-group" v-if="controlType === 'choice'" >
<div
class="choice-group"
@click="handleControlType('input')"
>
<a-icon class="choice-group-icon" type="caret-down" />
</div>
<treeselect
class="custom-treeselect input-group"
:style="{ '--custom-height': rowHeight }"
:value="choiceValue"
@input="(value) => handleChange('value', value)"
:multiple="false"
:clearable="false"
searchable
:options="curModelAttrList"
:placeholder="$t('placeholder2')"
:normalizer="
(node) => {
return {
id: node.name,
label: node.name,
children: node.children,
}
}
"
appendToBody
:zIndex="1050"
:disabled="disabled"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</div>
<div class="control-group" v-else>
<div
class="text-group"
@click="handleControlType('choice')"
>
<ops-icon class="text-group-icon" type="veops-text" />
</div>
<div
class="input-group"
v-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
>
<treeselect
class="custom-treeselect"
:style="{ '--custom-height': rowHeight }"
:value="rule.value"
@input="(value) => handleChange('value', value)"
:multiple="false"
:clearable="false"
searchable
:options="getChoiceValueByProperty(rule.property)"
:placeholder="$t('placeholder2')"
:normalizer="
(node) => {
return {
id: node[0],
label: node[0],
children: node.children,
}
}
"
appendToBody
:zIndex="1050"
:disabled="disabled"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</div>
<div
compact
v-else-if="rule.exp === 'range' || rule.exp === '~range'"
class="input-group"
>
<a-input
class="ops-input"
:placeholder="$t('min')"
:disabled="disabled"
:value="rule.min"
@change="(e) => handleChange('min', e.target.value)"
/>
<span class="input-group-range-icon">~</span>
<a-input
class="ops-input"
v-model="rule.max"
:placeholder="$t('max')"
:disabled="disabled"
:value="rule.max"
@change="(e) => handleChange('max', e.target.value)"
/>
</div>
<div class="input-group" compact v-else-if="rule.exp === 'compare'">
<treeselect
class="custom-treeselect"
:style="{ width: '70px', '--custom-height': rowHeight, 'flex-shrink': 0 }"
:value="rule.compareType"
@input="(value) => handleChange('compareType', value)"
:multiple="false"
:clearable="false"
searchable
:options="compareTypeList"
:normalizer="
(node) => {
return {
id: node.value,
label: node.label,
children: node.children,
}
}
"
appendToBody
:zIndex="1050"
:disabled="disabled"
>
</treeselect>
<a-input :value="rule.value" @change="(e) => handleChange('value', e.target.value)" class="ops-input"/>
</div>
<div class="input-group" v-else-if="rule.exp !== 'value' && rule.exp !== '~value'">
<a-input
:value="rule.value"
@change="(e) => handleChange('value', e.target.value)"
:placeholder="rule.exp === 'in' || rule.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
class="ops-input"
:disabled="disabled"
></a-input>
</div>
<div v-else :style="{ width: '136px' }"></div>
</div>
</div>
</template>
<script>
import { compareTypeList } from '../constants.js'
export default {
name: 'ValueControls',
props: {
rule: {
type: Object,
default: () => {},
},
attrList: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
// 当前模型属性
curModelAttrList: {
type: Array,
default: () => []
},
// 行高
rowHeight: {
type: String,
default: ''
}
},
data() {
return {
compareTypeList,
controlType: 'input',
}
},
computed: {
choiceValue() {
const regex = /\{\{([^}]+)\}\}/g
const val = regex.exec(this?.rule?.value || '')
return val ? val?.[1]?.trim() || '' : this?.value?.value || ''
}
},
methods: {
isChoiceByProperty(property) {
const _find = this.attrList.find((item) => item.name === property)
if (_find) {
return _find.is_choice
}
return false
},
getChoiceValueByProperty(property) {
const _find = this.attrList.find((item) => item.name === property)
if (_find) {
return _find.choice_value
}
return []
},
handleControlType(type) {
this.controlType = type
},
handleChange(key, value) {
if (this.controlType === 'choice' && key === 'value') {
value = `{{ ${value} }}`
}
this.$emit('change', {
...this.rule,
[key]: value
})
}
}
}
</script>
<style lang="less" scoped>
.control-group {
display: flex;
}
.input-group {
display: flex;
align-items: center;
width: 136px;
&-range-icon {
margin: 0 8px;
}
input {
height: 36px;
}
}
.choice-group {
width: 14px;
height: 36px;
flex-shrink: 0;
background-color: #00B3CC;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&-icon {
font-size: 12px;
color: #FFFFFF;
}
}
.text-group {
width: 14px;
height: 36px;
flex-shrink: 0;
background-color: #2F54EB;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&-icon {
font-size: 12px;
color: #FFFFFF;
}
}
</style>

View File

@ -0,0 +1,41 @@
import i18n from '@/lang'
export const ruleTypeList = () => {
return [
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
// { value: 'not', label: '非' },
]
}
export const expList = () => {
return [
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') },
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
]
}
export const advancedExpList = () => {
return [
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
]
}
export const compareTypeList = [
{ value: '1', label: '>' },
{ value: '2', label: '>=' },
{ value: '3', label: '<' },
{ value: '4', label: '<=' },
]

View File

@ -181,12 +181,19 @@
</span> </span>
</div> </div>
<div v-else class="cmdb-preference-footor-unsubscribed"> <div v-else class="cmdb-preference-footor-unsubscribed">
<span <a
@click="openSubscribeSetting(item)" @click="handleSubscribeCIType(item)"
><ops-icon :style="{ marginRight: '3px' }" type="cmdb-preference-subscribe" />{{ class="cmdb-preference-footor-unsubscribed-item"
$t('cmdb.preference.sub')
}}</span
> >
<ops-icon type="cmdb-ci" />{{ $t('cmdb.preference.subCITable') }}
</a>
<span class="cmdb-preference-footor-unsubscribed-gap"></span>
<a
@click="openSubscribeSetting(item, '2')"
class="cmdb-preference-footor-unsubscribed-item"
>
<ops-icon type="cmdb-tree" />{{ $t('cmdb.preference.subCITree') }}
</a>
</div> </div>
</div> </div>
<i></i><i></i><i></i><i></i><i></i> <i></i><i></i><i></i><i></i><i></i>
@ -221,6 +228,7 @@ import {
subscribeTreeView, subscribeTreeView,
preferenceCitypeOrder, preferenceCitypeOrder,
} from '@/modules/cmdb/api/preference' } from '@/modules/cmdb/api/preference'
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
import CollapseTransition from '@/components/CollapseTransition' import CollapseTransition from '@/components/CollapseTransition'
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting' import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
import { getCIAdcStatistics } from '../../api/ci' import { getCIAdcStatistics } from '../../api/ci'
@ -381,9 +389,39 @@ export default {
this.getCITypes() this.getCITypes()
}) })
}, },
async handleSubscribeCIType(ciType) {
try {
const res = await getCITypeAttributesByName(ciType.id)
const attributes = res?.attributes || []
const subscribeList = attributes
.filter((item) => item?.default_show)
.map((item) => {
return [item?.id?.toString(), false]
})
if (subscribeList.length === 0) {
const uniqueItem = attributes.find((item) => item?.id === res?.unique_id)
if (uniqueItem) {
subscribeList.push([uniqueItem?.id?.toString(), false])
}
}
await subscribeCIType(
ciType.id,
subscribeList
)
this.$message.success(this.$t('cmdb.components.subSuccess'))
this.resetRoute()
} catch (error) {
console.error('handleSubscribeCIType failed', error)
this.$message.success(this.$t('cmdb.components.subFailed'))
}
},
openSubscribeSetting(ciType, activeKey = '1') { openSubscribeSetting(ciType, activeKey = '1') {
this.$refs.subscribeSetting.open({ ...ciType, type_id: ciType.id }, activeKey) this.$refs.subscribeSetting.open({ ...ciType, type_id: ciType.id }, activeKey)
}, },
changeGroupExpand(group) { changeGroupExpand(group) {
const _idx = this.expandKeys.findIndex((expand) => expand === group.id) const _idx = this.expandKeys.findIndex((expand) => expand === group.id)
if (_idx > -1) { if (_idx > -1) {
@ -653,11 +691,27 @@ export default {
} }
} }
.cmdb-preference-footor-unsubscribed { .cmdb-preference-footor-unsubscribed {
text-align: center; display: flex;
> span { align-items: center;
color: @primary-color; justify-content: space-between;
cursor: pointer; padding: 0 10px;
&-item {
display: flex;
align-items: center;
gap: 3px;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.76);
&:hover {
color: #1890ff;
}
}
&-gap {
width: 1px;
height: 18px;
background-color: #e8e8e8;
} }
} }
.cmdb-preference-footor-subscribed { .cmdb-preference-footor-subscribed {