diff --git a/cmdb-ui/src/modules/cmdb/api/history.js b/cmdb-ui/src/modules/cmdb/api/history.js index 7de88a7..aeff68a 100644 --- a/cmdb-ui/src/modules/cmdb/api/history.js +++ b/cmdb-ui/src/modules/cmdb/api/history.js @@ -1,89 +1,92 @@ -import { axios } from '@/utils/request' - -export function getCIHistory(ciId) { - return axios({ - url: `/v0.1/history/ci/${ciId}`, - method: 'GET' - }) -} - -export function getCIHistoryTable(params) { - return axios({ - url: `/v0.1/history/records/attribute`, - method: 'GET', - params: params - }) -} - -export function getRelationTable(params) { - return axios({ - url: `/v0.1/history/records/relation`, - method: 'GET', - params: params - }) -} - -export function getCITypesTable(params) { - return axios({ - url: `/v0.1/history/ci_types`, - method: 'GET', - params: params - }) -} - -export function getUsers(params) { - return axios({ - url: `/v1/acl/users/employee`, - method: 'GET', - params: params - }) -} - -export function getCiTriggers(params) { - return axios({ - url: `/v0.1/history/ci_triggers`, - method: 'GET', - params: params - }) -} - -export function getCiTriggersByCiId(ci_id, params) { - return axios({ - url: `/v0.1/history/ci_triggers/${ci_id}`, - method: 'GET', - 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 - }) -} - -export function getCIsBaseline(params) { - return axios({ - url: `/v0.1/ci/baseline`, - method: 'GET', - params - }) -} - -export function CIBaselineRollback(ciId, params) { - return axios({ - url: `/v0.1/ci/${ciId}/baseline/rollback`, - method: 'POST', - data: params - }) -} +import { axios } from '@/utils/request' + +export function getCIHistory(ciId) { + return axios({ + url: `/v0.1/history/ci/${ciId}`, + method: 'GET' + }) +} + +export function getCIHistoryTable(params) { + return axios({ + url: `/v0.1/history/records/attribute`, + method: 'GET', + params: params, + timeout: 30 * 1000 + }) +} + +export function getRelationTable(params) { + return axios({ + url: `/v0.1/history/records/relation`, + method: 'GET', + params: params, + timeout: 30 * 1000 + }) +} + +export function getCITypesTable(params) { + return axios({ + url: `/v0.1/history/ci_types`, + method: 'GET', + params: params, + timeout: 30 * 1000 + }) +} + +export function getUsers(params) { + return axios({ + url: `/v1/acl/users/employee`, + method: 'GET', + params: params + }) +} + +export function getCiTriggers(params) { + return axios({ + url: `/v0.1/history/ci_triggers`, + method: 'GET', + params: params + }) +} + +export function getCiTriggersByCiId(ci_id, params) { + return axios({ + url: `/v0.1/history/ci_triggers/${ci_id}`, + method: 'GET', + 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 + }) +} + +export function getCIsBaseline(params) { + return axios({ + url: `/v0.1/ci/baseline`, + method: 'GET', + params + }) +} + +export function CIBaselineRollback(ciId, params) { + return axios({ + url: `/v0.1/ci/${ciId}/baseline/rollback`, + method: 'POST', + data: params + }) +} 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 ffc3362..7511f1b 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue @@ -1,449 +1,463 @@ -<template> - <div :style="{ height: '100%' }"> - <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab"> - <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }"> - <a-icon type="share-alt" /> - {{ $t('cmdb.ci.share') }} - </a> - <a-tab-pane key="tab_1"> - <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span> - <div class="ci-detail-attr"> - <el-descriptions - :title="group.name || $t('other')" - :key="group.name" - v-for="group in attributeGroups" - border - :column="3" - > - <el-descriptions-item - :label="`${attr.alias || attr.name}`" - :key="attr.name" - v-for="attr in group.attributes" - > - <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> - </el-descriptions-item> - </el-descriptions> - </div> - </a-tab-pane> - <a-tab-pane key="tab_2"> - <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> - <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }"> - <ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> - </div> - </a-tab-pane> - <a-tab-pane key="tab_3"> - <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> - <div :style="{ padding: '24px', height: '100%' }"> - <a-space :style="{ 'margin-bottom': '10px', display: 'flex' }"> - <a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()"> - <ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }} - </a-button> - </a-space> - <ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" /> - <vxe-table - ref="xTable" - show-overflow - show-header-overflow - :data="ciHistory" - size="small" - :height="tableHeight" - highlight-hover-row - :span-method="mergeRowMethod" - :scroll-y="{ enabled: false, gt: 20 }" - :scroll-x="{ enabled: false, gt: 0 }" - border - resizable - class="ops-unstripe-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" - :title="$t('user')" - :filters="[]" - :filter-method="filterUsernameMethod" - ></vxe-table-column> - <vxe-table-column - field="operate_type" - :filters="[ - { value: 0, label: $t('new') }, - { value: 1, label: $t('delete') }, - { value: 2, 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" - :title="$t('cmdb.attribute')" - :filters="[]" - :filter-method="filterAttrMethod" - ></vxe-table-column> - <vxe-table-column field="old" :title="$t('cmdb.history.old')"> - <template #default="{ row }"> - <span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span> - <span v-else>{{ row.old }}</span> - </template> - </vxe-table-column> - <vxe-table-column field="new" :title="$t('cmdb.history.new')"> - <template #default="{ row }"> - <span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span> - <span v-else>{{ row.new }}</span> - </template> - </vxe-table-column> - </vxe-table> - </div> - </a-tab-pane> - <a-tab-pane key="tab_4"> - <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span> - <div :style="{ padding: '24px', height: '100%' }"> - <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 ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" /> - </div> - </a-tab-pane> - </a-tabs> - <a-empty - v-else - :image-style="{ - height: '100px', - }" - :style="{ paddingTop: '20%' }" - > - <img slot="image" :src="require('@/assets/data_empty.png')" /> - <span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span> - </a-empty> - </div> -</template> - -<script> -import _ from 'lodash' -import { Descriptions, DescriptionsItem } from 'element-ui' -import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' -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' -import CiRollbackForm from './ciRollbackForm.vue' -export default { - name: 'CiDetailTab', - components: { - ElDescriptions: Descriptions, - ElDescriptionsItem: DescriptionsItem, - CiDetailAttrContent, - CiDetailRelation, - TriggerTable, - RelatedItsmTable, - CiRollbackForm, - }, - props: { - typeId: { - type: Number, - required: true, - }, - treeViewsLevels: { - type: Array, - default: () => [], - }, - attributeHistoryTableHeight: { - type: Number, - default: null - } - }, - data() { - return { - ci: {}, - item: [], - attributeGroups: [], - activeTabKey: 'tab_1', - rowSpanMap: {}, - ciHistory: [], - ciId: null, - ci_types: [], - hasPermission: true, - itsmInstalled: true, - tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120), - } - }, - computed: { - windowHeight() { - return this.$store.state.windowHeight - }, - operateTypeMap() { - return { - 0: this.$t('new'), - 1: this.$t('delete'), - 2: this.$t('update'), - } - }, - }, - provide() { - return { - ci_types: () => { - return this.ci_types - }, - } - }, - inject: { - reload: { - from: 'reload', - default: null, - }, - handleSearch: { - from: 'handleSearch', - default: null, - }, - attrList: { - from: 'attrList', - default: () => [], - }, - }, - methods: { - async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { - this.activeTabKey = activeTabKey - if (activeTabKey === 'tab_2') { - this.$nextTick(() => { - this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey - }) - } - this.ciId = ciId - await this.getCI() - await this.judgeItsmInstalled() - if (this.hasPermission) { - this.getAttributes() - this.getCIHistory() - getCITypes().then((res) => { - this.ci_types = res.ci_types - }) - } - }, - getAttributes() { - getCITypeGroupById(this.typeId, { need_other: 1 }) - .then((res) => { - this.attributeGroups = res - }) - .catch((e) => {}) - }, - async getCI() { - await getCIById(this.ciId) - .then((res) => { - if (res.result.length) { - this.ci = res.result[0] - } else { - this.hasPermission = false - } - }) - .catch((e) => { - if (e.response.status === 404) { - this.itsmInstalled = false - } - }) - }, - async judgeItsmInstalled() { - await judgeItsmInstalled().catch((e) => { - this.itsmInstalled = false - }) - }, - - getCIHistory() { - getCIHistory(this.ciId) - .then((res) => { - this.ciHistory = res - - const rowSpanMap = {} - let startIndex = 0 - let startCount = 1 - res.forEach((item, index) => { - if (index === 0) { - return - } - if (res[index].record_id === res[startIndex].record_id) { - startCount += 1 - rowSpanMap[index] = 0 - if (index === res.length - 1) { - rowSpanMap[startIndex] = startCount - } - } else { - rowSpanMap[startIndex] = startCount - startIndex = index - startCount = 1 - if (index === res.length - 1) { - rowSpanMap[index] = 1 - } - } - }) - this.rowSpanMap = rowSpanMap - }) - .catch((e) => { - console.log(e) - }) - }, - changeTab(key) { - this.activeTabKey = key - if (key === 'tab_3') { - this.$nextTick(() => { - const $table = this.$refs.xTable - if ($table) { - const usernameColumn = $table.getColumnByField('username') - const attrColumn = $table.getColumnByField('attr_alias') - if (usernameColumn) { - const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] - $table.setFilter( - usernameColumn, - usernameList.map((item) => { - return { - value: item, - label: item, - } - }) - ) - } - if (attrColumn) { - $table.setFilter( - attrColumn, - this.attrList().map((attr) => { - return { value: attr.alias || attr.name, label: attr.alias || attr.name } - }) - ) - } - } - }) - } - }, - filterUsernameMethod({ value, row, column }) { - return row.username === value - }, - filterOperateMethod({ value, row, column }) { - return Number(row.operate_type) === Number(value) - }, - filterAttrMethod({ value, row, column }) { - return row.attr_alias === value - }, - refresh(editAttrName) { - this.getCI() - const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) - // 修改的字段为树形视图订阅的字段 则全部reload - setTimeout(() => { - if (_find) { - if (this.reload) { - this.reload() - } - } else { - if (this.handleSearch) { - this.handleSearch() - } - } - }, 500) - }, - mergeRowMethod({ row, _rowIndex, column, visibleData }) { - const fields = ['created_at', 'username'] - const cellValue1 = row['created_at'] - const cellValue2 = row['username'] - if (cellValue1 && cellValue2 && fields.includes(column.property)) { - const prevRow = visibleData[_rowIndex - 1] - let nextRow = visibleData[_rowIndex + 1] - if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { - return { rowspan: 0, colspan: 0 } - } else { - let countRowspan = 1 - while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { - nextRow = visibleData[++countRowspan + _rowIndex] - } - if (countRowspan > 1) { - return { rowspan: countRowspan, colspan: 1 } - } - } - } - }, - updateCIByself(params, editAttrName) { - const _ci = { ..._.cloneDeep(this.ci), ...params } - this.ci = _ci - const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) - // 修改的字段为树形视图订阅的字段 则全部reload - setTimeout(() => { - if (_find) { - if (this.reload) { - this.reload() - } - } else { - if (this.handleSearch) { - this.handleSearch() - } - } - }, 500) - }, - shareCi() { - const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}` - this.$copyText(text) - .then(() => { - this.$message.success(this.$t('copySuccess')) - }) - .catch(() => { - this.$message.error(this.$t('cmdb.ci.copyFailed')) - }) - }, - handleRollbackCI() { - this.$nextTick(() => { - this.$refs.ciRollbackForm.onOpen() - }) - }, - }, -} -</script> - -<style lang="less"> -.ci-detail-tab { - height: 100%; - .ant-tabs-content { - height: calc(100% - 45px); - .ant-tabs-tabpane { - height: 100%; - } - } - .ant-tabs-bar { - margin: 0; - } - .ant-tabs-extra-content { - line-height: 44px; - } - .ci-detail-attr { - height: 100%; - overflow: auto; - padding: 24px; - .el-descriptions-item__content { - cursor: default; - &:hover a { - opacity: 1 !important; - } - } - .el-descriptions:first-child > .el-descriptions__header { - margin-top: 0; - } - .el-descriptions__header { - margin-bottom: 5px; - margin-top: 20px; - } - .ant-form-item { - margin-bottom: 0; - } - .ant-form-item-control { - line-height: 19px; - } - } -} -</style> +<template> + <div :style="{ height: '100%' }"> + <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab"> + <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }"> + <a-icon type="share-alt" /> + {{ $t('cmdb.ci.share') }} + </a> + <a-tab-pane key="tab_1"> + <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span> + <div class="ci-detail-attr"> + <el-descriptions + :title="group.name || $t('other')" + :key="group.name" + v-for="group in attributeGroups" + border + :column="3" + > + <el-descriptions-item + :label="`${attr.alias || attr.name}`" + :key="attr.name" + v-for="attr in group.attributes" + > + <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> + </el-descriptions-item> + </el-descriptions> + </div> + </a-tab-pane> + <a-tab-pane key="tab_2"> + <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> + <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }"> + <ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> + </div> + </a-tab-pane> + <a-tab-pane key="tab_3"> + <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> + <div :style="{ padding: '24px', height: '100%' }"> + <a-space :style="{ 'margin-bottom': '10px', display: 'flex' }"> + <a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()"> + <ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }} + </a-button> + <a-button type="primary" class="ops-button-ghost" ghost @click="handleExport"> + <ops-icon type="veops-export" />{{ $t('export') }} + </a-button> + </a-space> + <ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" /> + <vxe-table + ref="xTable" + show-overflow + show-header-overflow + :data="ciHistory" + size="small" + :height="tableHeight" + highlight-hover-row + :span-method="mergeRowMethod" + :scroll-y="{ enabled: false, gt: 20 }" + :scroll-x="{ enabled: false, gt: 0 }" + border + resizable + class="ops-unstripe-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" + :title="$t('user')" + :filters="[]" + :filter-method="filterUsernameMethod" + ></vxe-table-column> + <vxe-table-column + field="operate_type" + :filters="[ + { value: 0, label: $t('new') }, + { value: 1, label: $t('delete') }, + { value: 2, 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" + :title="$t('cmdb.attribute')" + :filters="[]" + :filter-method="filterAttrMethod" + ></vxe-table-column> + <vxe-table-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')"> + <template #default="{ row }"> + <span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span> + <span v-else>{{ row.old }}</span> + </template> + </vxe-table-column> + <vxe-table-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')"> + <template #default="{ row }"> + <span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span> + <span v-else>{{ row.new }}</span> + </template> + </vxe-table-column> + </vxe-table> + </div> + </a-tab-pane> + <a-tab-pane key="tab_4"> + <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span> + <div :style="{ padding: '24px', height: '100%' }"> + <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 ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" /> + </div> + </a-tab-pane> + </a-tabs> + <a-empty + v-else + :image-style="{ + height: '100px', + }" + :style="{ paddingTop: '20%' }" + > + <img slot="image" :src="require('@/assets/data_empty.png')" /> + <span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span> + </a-empty> + </div> +</template> + +<script> +import _ from 'lodash' +import { Descriptions, DescriptionsItem } from 'element-ui' +import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' +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' +import CiRollbackForm from './ciRollbackForm.vue' +export default { + name: 'CiDetailTab', + components: { + ElDescriptions: Descriptions, + ElDescriptionsItem: DescriptionsItem, + CiDetailAttrContent, + CiDetailRelation, + TriggerTable, + RelatedItsmTable, + CiRollbackForm, + }, + props: { + typeId: { + type: Number, + required: true, + }, + treeViewsLevels: { + type: Array, + default: () => [], + }, + attributeHistoryTableHeight: { + type: Number, + default: null + } + }, + data() { + return { + ci: {}, + item: [], + attributeGroups: [], + activeTabKey: 'tab_1', + rowSpanMap: {}, + ciHistory: [], + ciId: null, + ci_types: [], + hasPermission: true, + itsmInstalled: true, + tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120), + } + }, + computed: { + windowHeight() { + return this.$store.state.windowHeight + }, + operateTypeMap() { + return { + 0: this.$t('new'), + 1: this.$t('delete'), + 2: this.$t('update'), + } + }, + }, + provide() { + return { + ci_types: () => { + return this.ci_types + }, + } + }, + inject: { + reload: { + from: 'reload', + default: null, + }, + handleSearch: { + from: 'handleSearch', + default: null, + }, + attrList: { + from: 'attrList', + default: () => [], + }, + }, + methods: { + async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { + this.activeTabKey = activeTabKey + if (activeTabKey === 'tab_2') { + this.$nextTick(() => { + this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey + }) + } + this.ciId = ciId + await this.getCI() + await this.judgeItsmInstalled() + if (this.hasPermission) { + this.getAttributes() + this.getCIHistory() + getCITypes().then((res) => { + this.ci_types = res.ci_types + }) + } + }, + getAttributes() { + getCITypeGroupById(this.typeId, { need_other: 1 }) + .then((res) => { + this.attributeGroups = res + }) + .catch((e) => {}) + }, + async getCI() { + await getCIById(this.ciId) + .then((res) => { + if (res.result.length) { + this.ci = res.result[0] + } else { + this.hasPermission = false + } + }) + .catch((e) => { + if (e.response.status === 404) { + this.itsmInstalled = false + } + }) + }, + async judgeItsmInstalled() { + await judgeItsmInstalled().catch((e) => { + this.itsmInstalled = false + }) + }, + + getCIHistory() { + getCIHistory(this.ciId) + .then((res) => { + this.ciHistory = res + + const rowSpanMap = {} + let startIndex = 0 + let startCount = 1 + res.forEach((item, index) => { + if (index === 0) { + return + } + if (res[index].record_id === res[startIndex].record_id) { + startCount += 1 + rowSpanMap[index] = 0 + if (index === res.length - 1) { + rowSpanMap[startIndex] = startCount + } + } else { + rowSpanMap[startIndex] = startCount + startIndex = index + startCount = 1 + if (index === res.length - 1) { + rowSpanMap[index] = 1 + } + } + }) + this.rowSpanMap = rowSpanMap + }) + .catch((e) => { + console.log(e) + }) + }, + changeTab(key) { + this.activeTabKey = key + if (key === 'tab_3') { + this.$nextTick(() => { + const $table = this.$refs.xTable + if ($table) { + const usernameColumn = $table.getColumnByField('username') + const attrColumn = $table.getColumnByField('attr_alias') + if (usernameColumn) { + const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] + $table.setFilter( + usernameColumn, + usernameList.map((item) => { + return { + value: item, + label: item, + } + }) + ) + } + if (attrColumn) { + $table.setFilter( + attrColumn, + this.attrList().map((attr) => { + return { value: attr.alias || attr.name, label: attr.alias || attr.name } + }) + ) + } + } + }) + } + }, + filterUsernameMethod({ value, row, column }) { + return row.username === value + }, + filterOperateMethod({ value, row, column }) { + return Number(row.operate_type) === Number(value) + }, + filterAttrMethod({ value, row, column }) { + return row.attr_alias === value + }, + refresh(editAttrName) { + this.getCI() + const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) + // 修改的字段为树形视图订阅的字段 则全部reload + setTimeout(() => { + if (_find) { + if (this.reload) { + this.reload() + } + } else { + if (this.handleSearch) { + this.handleSearch() + } + } + }, 500) + }, + mergeRowMethod({ row, _rowIndex, column, visibleData }) { + const fields = ['created_at', 'username'] + const cellValue1 = row['created_at'] + const cellValue2 = row['username'] + if (cellValue1 && cellValue2 && fields.includes(column.property)) { + const prevRow = visibleData[_rowIndex - 1] + let nextRow = visibleData[_rowIndex + 1] + if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { + return { rowspan: 0, colspan: 0 } + } else { + let countRowspan = 1 + while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { + nextRow = visibleData[++countRowspan + _rowIndex] + } + if (countRowspan > 1) { + return { rowspan: countRowspan, colspan: 1 } + } + } + } + }, + updateCIByself(params, editAttrName) { + const _ci = { ..._.cloneDeep(this.ci), ...params } + this.ci = _ci + const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) + // 修改的字段为树形视图订阅的字段 则全部reload + setTimeout(() => { + if (_find) { + if (this.reload) { + this.reload() + } + } else { + if (this.handleSearch) { + this.handleSearch() + } + } + }, 500) + }, + shareCi() { + const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}` + this.$copyText(text) + .then(() => { + this.$message.success(this.$t('copySuccess')) + }) + .catch(() => { + this.$message.error(this.$t('cmdb.ci.copyFailed')) + }) + }, + handleRollbackCI() { + this.$nextTick(() => { + this.$refs.ciRollbackForm.onOpen() + }) + }, + async handleExport() { + this.$refs.xTable.exportData({ + filename: this.$t('cmdb.ci.history'), + sheetName: 'Sheet1', + type: 'xlsx', + types: ['xlsx', 'csv', 'html', 'xml', 'txt'], + data: this.ciHistory, + isMerge: true, + isColgroup: true, + }) + } + }, +} +</script> + +<style lang="less"> +.ci-detail-tab { + height: 100%; + .ant-tabs-content { + height: calc(100% - 45px); + .ant-tabs-tabpane { + height: 100%; + } + } + .ant-tabs-bar { + margin: 0; + } + .ant-tabs-extra-content { + line-height: 44px; + } + .ci-detail-attr { + height: 100%; + overflow: auto; + padding: 24px; + .el-descriptions-item__content { + cursor: default; + &:hover a { + opacity: 1 !important; + } + } + .el-descriptions:first-child > .el-descriptions__header { + margin-top: 0; + } + .el-descriptions__header { + margin-bottom: 5px; + margin-top: 20px; + } + .ant-form-item { + margin-bottom: 0; + } + .ant-form-item-control { + line-height: 19px; + } + } +} +</style> diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue index 8178e27..fc73711 100644 --- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue @@ -7,6 +7,7 @@ @search="handleSearch" @searchFormReset="searchFormReset" @searchFormChange="searchFormChange" + @export="handleExport" ></search-form> <vxe-table ref="xTable" @@ -86,13 +87,13 @@ </template> </vxe-column> <vxe-column field="attr_alias" :title="$t('cmdb.history.attribute')"></vxe-column> - <vxe-column field="old" :title="$t('cmdb.history.old')"></vxe-column> - <vxe-column field="new" :title="$t('cmdb.history.new')"></vxe-column> + <vxe-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')"></vxe-column> + <vxe-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')"></vxe-column> </vxe-table> <pager :current-page.sync="queryParams.page" :page-size.sync="queryParams.page_size" - :page-sizes="[50, 100, 200]" + :page-sizes="[50, 100, 200, 500]" :total="total" :isLoading="loading" @change="onChange" @@ -391,6 +392,37 @@ export default { filterOption(input, option) { return option.componentOptions.children[0].text.indexOf(input) >= 0 }, + + async handleExport(params) { + const hide = this.$message.loading(this.$t('loading'), 0) + const res = await getCIHistoryTable({ + ...params, + page: this.queryParams.page, + page_size: this.queryParams.page_size, + }) + hide() + const data = [] + res.records.forEach((item) => { + item[0].type_id = this.handleTypeId(item[0].type_id) + item[1].forEach((subItem) => { + subItem.operate_type = this.handleOperateType(subItem.operate_type) + subItem.new = subItem.new || '' + subItem.old = subItem.old || '' + const tempObj = Object.assign(subItem, item[0]) + data.push(tempObj) + }) + }) + + this.$refs.xTable.exportData({ + filename: this.$t('cmdb.history.ciChange'), + sheetName: 'Sheet1', + type: 'xlsx', + types: ['xlsx', 'csv', 'html', 'xml', 'txt'], + isMerge: true, + isColgroup: true, + data, + }) + } }, } </script> diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue index 540b074..d7fb859 100644 --- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue +++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue @@ -5,6 +5,7 @@ @expandChange="handleExpandChange" @search="handleSearch" @searchFormReset="searchFormReset" + @export="handleExport" ></search-form> <vxe-table ref="xTable" @@ -122,7 +123,7 @@ <pager :current-page.sync="queryParams.page" :page-size.sync="queryParams.page_size" - :page-sizes="[50, 100, 200]" + :page-sizes="[50, 100, 200, 500]" :total="total" :isLoading="loading" @change="onChange" @@ -379,6 +380,58 @@ export default { filterOption(input, option) { return option.componentOptions.children[0].text.indexOf(input) >= 0 }, + + async handleExport(params) { + const hide = this.$message.loading(this.$t('loading'), 0) + const res = await getRelationTable({ + ...params, + page: this.queryParams.page, + page_size: this.queryParams.page_size, + }) + hide() + const data = [] + res.records.forEach((item) => { + item[1].forEach((subItem) => { + subItem.operate_type = this.handleOperateType(subItem.operate_type) + subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id) + subItem.first = res.cis[String(subItem.first_ci_id)] + subItem.second = res.cis[String(subItem.second_ci_id)] + + const tempObj = Object.assign(subItem, item[0]) + + tempObj.changeDescription = this.getExportChangeDescription(tempObj) + + data.push(tempObj) + }) + }) + + this.$refs.xTable.exportData({ + filename: this.$t('cmdb.history.relationChange'), + sheetName: 'Sheet1', + type: 'xlsx', + types: ['xlsx', 'csv', 'html', 'xml', 'txt'], + isMerge: true, + isColgroup: true, + data, + }) + }, + + getExportChangeDescription(item) { + const first = item.first ? `${item.first.ci_type_alias}${item.first.unique_alias && item.first[item.first.unique] ? `(${item.first.unique_alias}:${item.first[item.first.unique]})` : ''}` : '' + const second = item.second ? `${item.second.ci_type_alias}${item.second.unique_alias && item.second[item.second.unique] ? `(${item.second.unique_alias}:${item.second[item.second.unique]})` : ''}` : '' + let center = '' + if (item.changeDescription === this.$t('cmdb.history.noUpdate')) { + center = item.relation_type_id + } else if (item.operate_type.includes(this.$t('update'))) { + center = item.changeArr.join(';') + } else if (item.operate_type.includes(this.$t('new'))) { + center = item.relation_type_id + } else if (item.operate_type.includes(this.$t('delete'))) { + center = item.relation_type_id + } + + return `${first || ''} => ${center || ''} => ${second || ''}` + } }, } </script> diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue index 66d80c5..4afd340 100644 --- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue @@ -27,7 +27,7 @@ <a-select-option :value="Object.values(choice)[0]" v-for="(choice, index) in attr.choice_value" - :key="'Search_' + attr.name + index" + :key="'Search_' + attr.name + Object.values(choice)[0] + index" > {{ Object.keys(choice)[0] }} </a-select-option> @@ -42,7 +42,7 @@ hideDisabledOptions: true, defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], }" - v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'" + v-else-if="attr.value_type === '3'" /> <a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else /> </a-form-item> @@ -85,7 +85,7 @@ @change="onChange" format="YYYY-MM-DD HH:mm" :placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]" - v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'" + v-else-if="attr.value_type === '3'" :show-time="{ hideDisabledOptions: true, defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], @@ -101,6 +101,9 @@ <a-button type="primary" html-type="submit" @click="handleSearch"> {{ $t('query') }} </a-button> + <a-button :style="{ marginLeft: '8px' }" @click="handleExport"> + {{ $t('export') }} + </a-button> <a-button :style="{ marginLeft: '8px' }" @click="handleReset"> {{ $t('reset') }} </a-button> @@ -155,6 +158,13 @@ export default { this.$emit('search', this.queryParams) }, + handleExport() { + const queryParams = { + ...this.queryParams + } + this.$emit('export', queryParams) + }, + handleReset() { this.queryParams = { page: 1, diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue index 09f01f0..486b0a0 100644 --- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue @@ -5,6 +5,7 @@ @expandChange="handleExpandChange" @search="handleSearch" @searchFormReset="searchFormReset" + @export="handleExport" ></search-form> <vxe-table ref="xTable" @@ -121,7 +122,7 @@ export default { relationTypeList: null, typeList: null, userList: [], - pageSizeOptions: ['50', '100', '200'], + pageSizeOptions: ['50', '100', '200', '500'], isExpand: false, current: 1, pageSize: 50, @@ -508,6 +509,36 @@ export default { filterOption(input, option) { return option.componentOptions.children[0].text.indexOf(input) >= 0 }, + + async handleExport(params) { + const hide = this.$message.loading(this.$t('loading'), 0) + const res = await getCITypesTable({ + ...params, + page: this.queryParams.page, + page_size: this.queryParams.page_size, + }) + hide() + + res.result.forEach((item) => { + this.handleChangeDescription(item, item.operate_type) + item.operate_type = this.handleOperateType(item.operate_type) + item.type_id = this.handleTypeId(item.type_id) + item.uid = this.handleUID(item.uid) + if (item.operate_type.includes(this.$t('update'))) { + item.changeDescription = item.changeArr.join(';') + } + }) + + this.$refs.xTable.exportData({ + filename: this.$t('cmdb.history.ciTypeChange'), + sheetName: 'Sheet1', + type: 'xlsx', + types: ['xlsx', 'csv', 'html', 'xml', 'txt'], + isMerge: true, + isColgroup: true, + data: res.result, + }) + }, }, } </script>