diff --git a/cmdb-ui/src/modules/cmdb/api/history.js b/cmdb-ui/src/modules/cmdb/api/history.js index ae8a099..dcadc65 100644 --- a/cmdb-ui/src/modules/cmdb/api/history.js +++ b/cmdb-ui/src/modules/cmdb/api/history.js @@ -54,3 +54,20 @@ export function getCiTriggersByCiId(ci_id, params) { params }) } + +export function getCiRelatedTickets(params) { + return axios({ + url: `/itsm/v1/process_ticket/get_tickets_by`, + method: 'POST', + data: params, + isShowMessage: false + }) +} + +export function judgeItsmInstalled() { + return axios({ + url: `/itsm/v1/process_ticket/itsm_existed`, + method: 'GET', + isShowMessage: false + }) +} diff --git a/cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png b/cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png new file mode 100644 index 0000000..273716e Binary files /dev/null and b/cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png differ diff --git a/cmdb-ui/src/modules/cmdb/lang/en.js b/cmdb-ui/src/modules/cmdb/lang/en.js index 3d8e102..02d9aa2 100644 --- a/cmdb-ui/src/modules/cmdb/lang/en.js +++ b/cmdb-ui/src/modules/cmdb/lang/en.js @@ -402,7 +402,15 @@ const cmdb_en = { noModifications: 'No Modifications', attr: 'attribute', attrId: 'attribute id', - changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}' + changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}', + ticketStartTime: 'Start Time', + ticketCreator: 'Creator', + ticketTitle: 'Title', + ticketFinishTime: 'Finish Time', + ticketNodeName: 'Node Name', + itsmUninstalled: 'Please use it in combination with VE ITSM', + applyItsm: 'Free Apply ITSM', + ticketId: 'Ticket ID', }, relation_type: { addRelationType: 'New', diff --git a/cmdb-ui/src/modules/cmdb/lang/zh.js b/cmdb-ui/src/modules/cmdb/lang/zh.js index 762bcbb..1c1d524 100644 --- a/cmdb-ui/src/modules/cmdb/lang/zh.js +++ b/cmdb-ui/src/modules/cmdb/lang/zh.js @@ -402,7 +402,15 @@ const cmdb_zh = { noModifications: '没有修改', attr: '属性名', attrId: '属性ID', - changeDescription: '属性ID:{attr_id},提前:{before_days}天,主题:{subject}\n内容:{body}\n通知时间:{notify_at}' + changeDescription: '属性ID:{attr_id},提前:{before_days}天,主题:{subject}\n内容:{body}\n通知时间:{notify_at}', + ticketStartTime: '工单发起时间', + ticketCreator: '发起人', + ticketTitle: '工单名称', + ticketFinishTime: '节点完成时间', + ticketNodeName: '节点名称', + itsmUninstalled: '请结合维易ITSM使用', + applyItsm: '免费申请', + ticketId: '工单ID', }, relation_type: { addRelationType: '新增关系类型', diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelatedItsmTable.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelatedItsmTable.vue new file mode 100644 index 0000000..4bb10f9 --- /dev/null +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelatedItsmTable.vue @@ -0,0 +1,221 @@ +<template> + <div :style="{ height: '100%' }" v-if="itsmInstalled"> + <vxe-table + ref="xTable" + show-overflow + show-header-overflow + resizable + border + size="small" + class="ops-stripe-table" + :span-method="mergeRowMethod" + :data="tableData" + v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }" + > + <template #empty> + <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }"> + <img slot="image" :src="require('@/assets/data_empty.png')" /> + <span slot="description"> {{ $t('noData') }} </span> + </a-empty> + </template> + <vxe-column field="ticket.ticket_id" min-width="80" :title="$t('cmdb.history.ticketId')"> </vxe-column> + <vxe-column field="ticket.created_at" width="160" :title="$t('cmdb.history.ticketStartTime')"> </vxe-column> + <vxe-column field="ticket.creator_name" min-width="80" :title="$t('cmdb.history.ticketCreator')"> </vxe-column> + <vxe-column field="ticket.title" min-width="150" :title="$t('cmdb.history.ticketTitle')"> + <template slot-scope="{ row }"> + <a target="_blank" :href="row.ticket.url">{{ row.ticket.title }}</a> + </template> + </vxe-column> + <vxe-column field="ticket.node_finish_time" width="160" :title="$t('cmdb.history.ticketFinishTime')"> + </vxe-column> + <vxe-column field="ticket.node_name" min-width="100" :title="$t('cmdb.history.ticketNodeName')"> </vxe-column> + <vxe-table-column + field="operate_type" + min-width="100" + :filters="[ + { value: 0, label: $t('new') }, + { value: 1, label: $t('delete') }, + { value: 3, label: $t('update') }, + ]" + :filter-method="filterOperateMethod" + :title="$t('operation')" + > + <template #default="{ row }"> + {{ operateTypeMap[row.operate_type] }} + </template> + </vxe-table-column> + <vxe-table-column + field="attr_alias" + min-width="100" + :title="$t('cmdb.attribute')" + :filters="[]" + :filter-method="filterAttrMethod" + > + </vxe-table-column> + <vxe-table-column field="old" min-width="100" :title="$t('cmdb.history.old')"></vxe-table-column> + <vxe-table-column field="new" min-width="100" :title="$t('cmdb.history.new')"></vxe-table-column> + </vxe-table> + <div :style="{ textAlign: 'right' }" v-if="!ci_id"> + <a-pagination + size="small" + show-size-changer + show-quick-jumper + :page-size-options="pageSizeOptions" + :current="tablePage.currentPage" + :total="tablePage.totalResult" + :show-total="(total, range) => $t('cmdb.history.totalItems', { total: total })" + :page-size="tablePage.pageSize" + :default-current="1" + @change="pageOrSizeChange" + @showSizeChange="pageOrSizeChange" + > + </a-pagination> + </div> + </div> + <a-empty + v-else + :image-style="{ + height: '200px', + }" + :style="{ paddingTop: '10%' }" + > + <img slot="image" :src="require('@/modules/cmdb/assets/itsm_uninstalled.png')" /> + <span slot="description"> {{ $t('cmdb.history.itsmUninstalled') }} </span> + <a-button href="https://veops.cn/apply" target="_blank" type="primary"> + {{ $t('cmdb.history.applyItsm') }} + </a-button> + </a-empty> +</template> + +<script> +import { getCiRelatedTickets } from '../../../api/history' +export default { + name: 'RelatedItsmTable', + props: { + ci_id: { + type: Number, + default: null, + }, + ciHistory: { + type: Array, + default: () => [], + }, + itsmInstalled: { + type: Boolean, + default: true, + }, + }, + data() { + return { + tableData: [], + tablePage: { + currentPage: 1, + pageSize: 50, + totalResult: 0, + }, + pageSizeOptions: ['50', '100', '200'], + } + }, + computed: { + windowHeight() { + return this.$store.state.windowHeight + }, + operateTypeMap() { + return { + 0: this.$t('new'), + 1: this.$t('delete'), + 2: this.$t('update'), + } + }, + }, + mounted() { + this.updateTableData() + }, + methods: { + updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) { + const params = { page: currentPage, page_size: pageSize, next_todo_ids: [] } + if (this.ci_id) { + const tableData = [] + this.ciHistory.forEach((item) => { + if (item.ticket_id) { + params.next_todo_ids.push(item.ticket_id) + tableData.push(item) + } + }) + if (params.next_todo_ids.length) { + getCiRelatedTickets(params) + .then((res) => { + const ticketId2Detail = {} + res.forEach((item) => { + ticketId2Detail[item.next_todo_id] = item + }) + this.tableData = tableData.map((item) => { + return { + ...item, + ticket: ticketId2Detail[item.ticket_id], + } + }) + this.updateAttrFilter() + }) + .catch((e) => {}) + } + } else { + } + }, + updateAttrFilter() { + this.$nextTick(() => { + const $table = this.$refs.xTable + if ($table) { + const attrColumn = $table.getColumnByField('attr_alias') + if (attrColumn) { + $table.setFilter( + attrColumn, + this.tableData.map((item) => { + return { value: item.attr_alias, label: item.attr_alias } + }) + ) + } + } + }) + }, + mergeRowMethod({ row, _rowIndex, column, visibleData }) { + const fields = [ + 'ticket.ticket_id', + 'ticket.created_at', + 'ticket.creator_name', + 'ticket.title', + 'ticket.node_finish_time', + 'ticket.node_name', + ] + const cellValue1 = row.ticket.ticket_id + const cellValue2 = row.ticket.node_name + if (cellValue1 && cellValue2 && fields.includes(column.property)) { + const prevRow = visibleData[_rowIndex - 1] + let nextRow = visibleData[_rowIndex + 1] + if (prevRow && prevRow.ticket.ticket_id === cellValue1 && prevRow.ticket.node_name === cellValue2) { + return { rowspan: 0, colspan: 0 } + } else { + let countRowspan = 1 + while (nextRow && nextRow.ticket.ticket_id === cellValue1 && nextRow.ticket.node_name === cellValue2) { + nextRow = visibleData[++countRowspan + _rowIndex] + } + if (countRowspan > 1) { + return { rowspan: countRowspan, colspan: 1 } + } + } + } + }, + pageOrSizeChange(currentPage, pageSize) { + this.updateTableData(currentPage, pageSize) + }, + filterOperateMethod({ value, row, column }) { + return Number(row.operate_type) === Number(value) + }, + filterAttrMethod({ value, row, column }) { + return row.attr_alias === value + }, + }, +} +</script> + +<style></style> diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue index 11491e1..a2ff1b2 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue @@ -41,9 +41,16 @@ height="auto" :span-method="mergeRowMethod" border + resizable :scroll-y="{ enabled: false }" class="ops-stripe-table" > + <template #empty> + <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }"> + <img slot="image" :src="require('@/assets/data_empty.png')" /> + <span slot="description"> {{ $t('noData') }} </span> + </a-empty> + </template> <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column> <vxe-table-column field="username" @@ -82,6 +89,12 @@ <TriggerTable :ci_id="ci._id" /> </div> </a-tab-pane> + <a-tab-pane key="tab_5"> + <span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span> + <div :style="{ padding: '24px', height: '100%' }"> + <RelatedItsmTable :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" /> + </div> + </a-tab-pane> </a-tabs> <a-empty v-else @@ -100,11 +113,12 @@ import _ from 'lodash' import { Descriptions, DescriptionsItem } from 'element-ui' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' -import { getCIHistory } from '@/modules/cmdb/api/history' +import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history' import { getCIById } from '@/modules/cmdb/api/ci' import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailRelation from './ciDetailRelation.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue' +import RelatedItsmTable from './ciDetailRelatedItsmTable.vue' export default { name: 'CiDetailTab', components: { @@ -113,6 +127,7 @@ export default { CiDetailAttrContent, CiDetailRelation, TriggerTable, + RelatedItsmTable, }, props: { typeId: { @@ -134,6 +149,7 @@ export default { ciId: null, ci_types: [], hasPermission: true, + itsmInstalled: true, } }, computed: { @@ -179,6 +195,7 @@ export default { } this.ciId = ciId await this.getCI() + await this.judgeItsmInstalled() if (this.hasPermission) { this.getAttributes() this.getCIHistory() @@ -203,7 +220,15 @@ export default { this.hasPermission = false } }) - .catch((e) => {}) + .catch((e) => { + if (e.response.status === 404) { + this.itsmInstalled = false + } + }) + }, + async judgeItsmInstalled() { + await judgeItsmInstalled() + .catch((e) => { this.itsmInstalled = false }) }, getCIHistory() { diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/triggerTable.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/triggerTable.vue index 2d95c29..b084d4e 100644 --- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/triggerTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/triggerTable.vue @@ -9,6 +9,12 @@ :data="tableData" v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }" > + <template #empty> + <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }"> + <img slot="image" :src="require('@/assets/data_empty.png')" /> + <span slot="description"> {{ $t('noData') }} </span> + </a-empty> + </template> <vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column> <vxe-column field="type" :title="$t('type')"> <template #default="{ row }">