mirror of
https://github.com/jumpserver/lina.git
synced 2025-09-20 02:31:43 +00:00
feat: 添加申请资源工单功能 (#185)
* [Feature] 添加申请资产工单 * feat: 添加资产申请工单功能 * update * feat: 添加申请资源工单功能 * feat: 添加申请资源工单功能 * feat: 添加申请资源工单功能 * feat: 添加申请资源工单功能 * fix(终端列表): 还原终端列表的代码 * fix: 修改申请资源工单功能 * fix: 修改申请资源工单功能 * fix: 修改申请资源工单功能 * feat: 添加请求资产权限工单 * Update cn.json * Update en.json Co-authored-by: xinwen <coderWen@126.com>
This commit is contained in:
@@ -6,10 +6,14 @@
|
|||||||
<el-col :span="18"><div class="item-text">{{ this.$route.params.id }}</div></el-col>
|
<el-col :span="18"><div class="item-text">{{ this.$route.params.id }}</div></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-for="item in items" :key="'card-' + item.key" :gutter="10" class="item">
|
<el-row v-for="item in items" :key="'card-' + item.key" :gutter="10" class="item">
|
||||||
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div></el-col>
|
<el-col :span="6">
|
||||||
<el-col :span="18"><div class="item-text">
|
<div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div>
|
||||||
<ItemValue :value="item.value" v-bind="item" />
|
</el-col>
|
||||||
</div></el-col>
|
<el-col :span="18">
|
||||||
|
<div class="item-text">
|
||||||
|
<ItemValue :value="item.value" v-bind="item" />
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -37,7 +37,6 @@
|
|||||||
import Select2 from '../Select2'
|
import Select2 from '../Select2'
|
||||||
import IBox from '../IBox'
|
import IBox from '../IBox'
|
||||||
import { createSourceIdCache } from '@/api/common'
|
import { createSourceIdCache } from '@/api/common'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RelationCard',
|
name: 'RelationCard',
|
||||||
components: {
|
components: {
|
||||||
|
@@ -177,6 +177,9 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadMore(load) {
|
async loadMore(load) {
|
||||||
|
if (!this.iAjax.url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.params.hasMore) {
|
if (!this.params.hasMore) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -148,6 +148,7 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"Action": "动作",
|
"Action": "动作",
|
||||||
|
"RequestTickets": "请求工单",
|
||||||
"Actions": "操作",
|
"Actions": "操作",
|
||||||
"Activate": "激活",
|
"Activate": "激活",
|
||||||
"Active": "激活中",
|
"Active": "激活中",
|
||||||
@@ -218,6 +219,7 @@
|
|||||||
"bulkDeleteErrorMsg": "批量删除失败: ",
|
"bulkDeleteErrorMsg": "批量删除失败: ",
|
||||||
"bulkDeleteSuccessMsg": "批量删除成功",
|
"bulkDeleteSuccessMsg": "批量删除成功",
|
||||||
"bulkRemoveErrorMsg": "批量移除失败: ",
|
"bulkRemoveErrorMsg": "批量移除失败: ",
|
||||||
|
"NeedAssetsAndSystemUserErrMsg": "请先选择授权的系统用户和资产",
|
||||||
"bulkRemoveSuccessMsg": "批量移除成功",
|
"bulkRemoveSuccessMsg": "批量移除成功",
|
||||||
"createBy": "创建者",
|
"createBy": "创建者",
|
||||||
"createErrorMsg": "创建失败",
|
"createErrorMsg": "创建失败",
|
||||||
@@ -711,6 +713,11 @@
|
|||||||
"user": "用户",
|
"user": "用户",
|
||||||
"Status": "状态",
|
"Status": "状态",
|
||||||
"Open": "打开",
|
"Open": "打开",
|
||||||
|
"IP": "IP",
|
||||||
|
"Hostname": "主机名",
|
||||||
|
"Asset": "资产",
|
||||||
|
"SystemUser": "系统用户",
|
||||||
|
"RequestAssetPerm": "申请资产授权",
|
||||||
"Applicant": "申请人",
|
"Applicant": "申请人",
|
||||||
"Pending": "未处理",
|
"Pending": "未处理",
|
||||||
"Approved": "已同意",
|
"Approved": "已同意",
|
||||||
|
@@ -148,6 +148,7 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"Nothing": "Nothing",
|
"Nothing": "Nothing",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
|
"RequestTickets": "Request tickets",
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Activate": "Activate",
|
"Activate": "Activate",
|
||||||
"actionsTips":"Clipboard's copy and paste control only support RDP/VNC protocol.",
|
"actionsTips":"Clipboard's copy and paste control only support RDP/VNC protocol.",
|
||||||
@@ -219,6 +220,7 @@
|
|||||||
"bulkDeleteSuccessMsg": "Bulk delete success",
|
"bulkDeleteSuccessMsg": "Bulk delete success",
|
||||||
"bulkRemoveErrorMsg": "Bulk remove failed: ",
|
"bulkRemoveErrorMsg": "Bulk remove failed: ",
|
||||||
"bulkRemoveSuccessMsg": "Bulk remove success",
|
"bulkRemoveSuccessMsg": "Bulk remove success",
|
||||||
|
"NeedAssetsAndSystemUserErrMsg": "Need assets and systemuser",
|
||||||
"createBy": "Create by",
|
"createBy": "Create by",
|
||||||
"createErrorMsg": "Create error",
|
"createErrorMsg": "Create error",
|
||||||
"createSuccessMsg": "Create success",
|
"createSuccessMsg": "Create success",
|
||||||
@@ -710,12 +712,15 @@
|
|||||||
"user": "User",
|
"user": "User",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"Open": "Open",
|
"Open": "Open",
|
||||||
|
"IP": "IP",
|
||||||
|
"Hostname": "Hostname",
|
||||||
|
"Asset": "Asset",
|
||||||
|
"SystemUser": "System user",
|
||||||
"Applicant": "Applicant",
|
"Applicant": "Applicant",
|
||||||
"Pending": "Pending",
|
"Pending": "Pending",
|
||||||
"Approved": "Approved",
|
"Approved": "Approved",
|
||||||
"Rejected": "Rejected",
|
"Rejected": "Rejected",
|
||||||
"Closed": "Closed"
|
"Closed": "Closed"
|
||||||
|
|
||||||
},
|
},
|
||||||
"tree": {
|
"tree": {
|
||||||
"AddAssetToNode": "Add asset to node",
|
"AddAssetToNode": "Add asset to node",
|
||||||
|
@@ -4,7 +4,7 @@ export default [
|
|||||||
path: 'tickets',
|
path: 'tickets',
|
||||||
name: 'TicketList',
|
name: 'TicketList',
|
||||||
component: () => import('@/views/tickets/TicketList'),
|
component: () => import('@/views/tickets/TicketList'),
|
||||||
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o' }
|
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o', activeMenu: '/tickets/tickets' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tickets/:id',
|
path: 'tickets/:id',
|
||||||
@@ -12,5 +12,19 @@ export default [
|
|||||||
component: () => import('@/views/tickets/TicketDetail/index'),
|
component: () => import('@/views/tickets/TicketDetail/index'),
|
||||||
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||||
hidden: true
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tickets/request-asset-perm/create',
|
||||||
|
name: 'RequestAssetPermTicketCreateUpdate',
|
||||||
|
component: () => import('@/views/tickets/RequestAssetPerm/RequestAssetPermTicketCreateUpdate'),
|
||||||
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tickets/request-asset-perm/:id',
|
||||||
|
name: 'AssetsTicketDetail',
|
||||||
|
component: () => import('@/views/tickets/RequestAssetPerm/Detail/index'),
|
||||||
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -66,6 +66,45 @@ export default [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tickets',
|
||||||
|
component: Layout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'TicketList',
|
||||||
|
component: () => import('@/views/tickets/TicketList'),
|
||||||
|
meta: { title: i18n.t('route.Tickets'), icon: 'check-square-o', activeMenu: '/tickets', permissions: [rolec.PERM_USE] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tickets/request-asset-perm/create',
|
||||||
|
name: 'RequestAssetPermTicketCreateUpdate',
|
||||||
|
component: () => import('@/views/tickets/RequestAssetPerm/RequestAssetPermTicketCreateUpdate'),
|
||||||
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tickets/request-asset-perm/:id',
|
||||||
|
name: 'AssetsTicketDetail',
|
||||||
|
component: () => import('@/views/tickets/RequestAssetPerm/Detail/index'),
|
||||||
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tickets/:id',
|
||||||
|
name: 'TicketDetail',
|
||||||
|
component: () => import('@/views/tickets/TicketDetail/index'),
|
||||||
|
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets', permissions: [rolec.PERM_USE] },
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: i18n.t('route.Tickets'),
|
||||||
|
icon: 'history',
|
||||||
|
permissions: [rolec.PERM_USE],
|
||||||
|
licenseRequired: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `external-luna`,
|
path: `external-luna`,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
@@ -136,6 +136,13 @@ export function getDaysAgo(days, now) {
|
|||||||
return new Date(now.getTime() - 3600 * 1000 * 24 * days)
|
return new Date(now.getTime() - 3600 * 1000 * 24 * days)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDaysFuture(days, now) {
|
||||||
|
if (!now) {
|
||||||
|
now = new Date()
|
||||||
|
}
|
||||||
|
return new Date(now.getTime() + 3600 * 1000 * 24 * days)
|
||||||
|
}
|
||||||
|
|
||||||
export function setUrlParam(url, name, value) {
|
export function setUrlParam(url, name, value) {
|
||||||
const urlArray = url.split('?')
|
const urlArray = url.split('?')
|
||||||
if (urlArray.length === 1) {
|
if (urlArray.length === 1) {
|
||||||
|
@@ -18,7 +18,6 @@ import { GenericListPage, GenericCreateUpdateForm } from '@/layout/components'
|
|||||||
import Dialog from '@/components/Dialog'
|
import Dialog from '@/components/Dialog'
|
||||||
import Select2 from '@/components/Select2'
|
import Select2 from '@/components/Select2'
|
||||||
import { BooleanFormatter } from '@/components/ListTable/formatters'
|
import { BooleanFormatter } from '@/components/ListTable/formatters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GenericListPage,
|
GenericListPage,
|
||||||
@@ -155,5 +154,4 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<TicketListTable :url="url" />
|
<TicketListTable :url="url" :has-more-actions="true" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<el-select v-model="value" v-bind="$attrs" class="select2" v-on="$listeners">
|
||||||
|
<el-option-group
|
||||||
|
v-for="group in options"
|
||||||
|
:key="group.org_name"
|
||||||
|
:label="group.org_name"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in group.org_admins"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name + '(' + item.username + ')'"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-option-group>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'GroupSelectFormatter',
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: '',
|
||||||
|
options: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$axios.get(this.url).then(
|
||||||
|
res => {
|
||||||
|
this.options = res
|
||||||
|
console.log(this.options)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.select2 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
29
src/views/tickets/RequestAssetPerm/Detail/ItemValue.vue
Normal file
29
src/views/tickets/RequestAssetPerm/Detail/ItemValue.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script type="text/jsx">
|
||||||
|
export default {
|
||||||
|
name: 'ItemValue',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Function, Array, Object],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formatter: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render(h) {
|
||||||
|
if (typeof this.formatter === 'function') {
|
||||||
|
return this.formatter(this.item, this.value)
|
||||||
|
}
|
||||||
|
return <span>{this.value}</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
306
src/views/tickets/RequestAssetPerm/Detail/TicketDetail.vue
Normal file
306
src/views/tickets/RequestAssetPerm/Detail/TicketDetail.vue
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
<template>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="18">
|
||||||
|
<IBox>
|
||||||
|
<div slot="header" class="clearfix ibox-title">
|
||||||
|
<i class="fa fa-info-circle" />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col v-for="item in detailCardItems" :key="'card-' + item.key" :span="12">
|
||||||
|
<el-row class="item">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div :style="{ 'text-align': 'align' }" class="item-label">
|
||||||
|
<label>{{ item.key }}: </label>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="18">
|
||||||
|
<div class="item-text">
|
||||||
|
<ItemValue :value="item.value" v-bind="item" />
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template v-if="hasActionPerm&&object.status !== 'closed'">
|
||||||
|
<el-form ref="request_form" :model="request_form" label-width="140px" label-position="left" class="assets">
|
||||||
|
<el-form-item :label="$t('tickets.Asset')" required>
|
||||||
|
<Select2 ref="select2" v-model="request_form.asset" v-bind="asset_select2" style="width: 30% !important" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('tickets.SystemUser')" required>
|
||||||
|
<Select2 ref="select2" v-model="request_form.systemuser" v-bind="systemuser_select2" style="width: 30% !important" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<div class="feed-activity-list">
|
||||||
|
<div class="feed-element">
|
||||||
|
<a href="#" class="pull-left">
|
||||||
|
<el-avatar :src="imageUrl" class="header-avatar" />
|
||||||
|
</a>
|
||||||
|
<div class="media-body ">
|
||||||
|
<strong>{{ object.user_display }}</strong> <small class="text-muted"> {{ formatTime(object.date_created) }}</small>
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">{{ toSafeLocalDateStr(object.date_created) }} </small>
|
||||||
|
<div style="padding-top: 10px">
|
||||||
|
<span v-html="object.body" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="comments">
|
||||||
|
<div v-for="item in comments" :key="item.user_display + item.body">
|
||||||
|
<div class="feed-element">
|
||||||
|
<a href="#" class="pull-left">
|
||||||
|
<el-avatar :src="imageUrl" class="header-avatar" />
|
||||||
|
</a>
|
||||||
|
<div class="media-body ">
|
||||||
|
<strong>{{ item.user_display }}</strong> <small class="text-muted">{{ formatTime(item.date_created) }}</small>
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">{{ toSafeLocalDateStr(item.date_created) }}</small>
|
||||||
|
<div style="padding-top: 10px">
|
||||||
|
{{ item.body }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="comments" :model="form" label-width="45px" style="padding-top: 20px">
|
||||||
|
<el-form-item :label="$t('tickets.reply')">
|
||||||
|
<el-input v-model="form.comments" :autosize="{ minRows: 4 }" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item style="float: right">
|
||||||
|
<template v-if="hasActionPerm">
|
||||||
|
<el-button :disabled="object.status === 'closed'" type="primary" size="small" @click="handleApprove"><i class="fa fa-check" />{{ $t('tickets.Accept') }}</el-button>
|
||||||
|
<el-button :disabled="object.status === 'closed'" type="warning" size="small" @click="handleReject"><i class="fa fa-ban" />{{ $t('tickets.Reject') }}</el-button>
|
||||||
|
</template>
|
||||||
|
<el-button :disabled="object.status === 'closed'" type="danger" size="small" @click="handleClosed"><i class="fa fa-times" />{{ $t('tickets.Close') }}</el-button>
|
||||||
|
<el-button :disabled="object.status === 'closed'" type="info" size="small" @click="handleComment"><i class="fa fa-pencil" />{{ $t('tickets.Comment') }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</IBox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import IBox from '@/components/IBox'
|
||||||
|
import ItemValue from './ItemValue'
|
||||||
|
import Select2 from '@/components/Select2'
|
||||||
|
import { toSafeLocalDateStr } from '@/utils/common'
|
||||||
|
import { formatTime, getDateTimeStamp } from '@/utils'
|
||||||
|
export default {
|
||||||
|
name: '',
|
||||||
|
components: { IBox, ItemValue, Select2 },
|
||||||
|
props: {
|
||||||
|
object: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageUrl: require('@/assets/img/admin.png'),
|
||||||
|
form: {
|
||||||
|
comments: '',
|
||||||
|
confirmed_assets: []
|
||||||
|
},
|
||||||
|
request_form: {
|
||||||
|
asset: this.object.confirmed_assets,
|
||||||
|
systemuser: ''
|
||||||
|
},
|
||||||
|
comments: '',
|
||||||
|
assets: [],
|
||||||
|
asset_select2: {
|
||||||
|
multiple: true,
|
||||||
|
value: this.object.confirmed_assets,
|
||||||
|
ajax: {
|
||||||
|
url: this.object.assets_waitlist_url,
|
||||||
|
transformOption: (item) => {
|
||||||
|
return { label: item.hostname, value: item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
systemuser_select2: {
|
||||||
|
multiple: false,
|
||||||
|
ajax: {
|
||||||
|
url: this.object.system_user_waitlist_url,
|
||||||
|
transformOption: (item) => {
|
||||||
|
return { label: item.name + '(' + item.username + ')', value: item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
detailCardItems() {
|
||||||
|
const vm = this
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.status'),
|
||||||
|
value: this.object.status,
|
||||||
|
formatter: function(row, data) {
|
||||||
|
const open = vm.$t('common.Open')
|
||||||
|
const close = vm.$t('common.Close')
|
||||||
|
if (data === 'open') {
|
||||||
|
return <el-button type='primary' size='mini'>{open}</el-button>
|
||||||
|
}
|
||||||
|
return <el-button type='danger' size='mini'>{close}</el-button>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.type'),
|
||||||
|
value: this.object.type_display
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.user'),
|
||||||
|
value: this.object.user_display
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.Assignees'),
|
||||||
|
value: this.object.assignees_display
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.Assignee'),
|
||||||
|
value: this.object.assignee_display
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.IP'),
|
||||||
|
value: this.object.ips
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('tickets.Hostname'),
|
||||||
|
value: this.object.hostname
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('common.dateCreated'),
|
||||||
|
value: toSafeLocalDateStr(this.object.date_created)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('common.dateStart'),
|
||||||
|
value: toSafeLocalDateStr(this.object.date_start)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: this.$t('common.dateExpired'),
|
||||||
|
value: toSafeLocalDateStr(this.object.date_expired)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
hasActionPerm() {
|
||||||
|
return this.object.assignees.indexOf(this.$store.state.users.profile.id) !== -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const url = `/api/v1/tickets/tickets/${this.object.id}/comments/`
|
||||||
|
this.$axios.get(url).then(res => {
|
||||||
|
this.comments = res
|
||||||
|
}).catch(err => {
|
||||||
|
this.$message.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatTime(dateStr) {
|
||||||
|
return formatTime(getDateTimeStamp(dateStr))
|
||||||
|
},
|
||||||
|
toSafeLocalDateStr(dataStr) {
|
||||||
|
return toSafeLocalDateStr(dataStr)
|
||||||
|
},
|
||||||
|
reloadPage() {
|
||||||
|
window.location.reload()
|
||||||
|
},
|
||||||
|
createComment(successCallback) {
|
||||||
|
const commentText = this.form.comments
|
||||||
|
const ticketId = this.object.id
|
||||||
|
const commentUrl = `/api/v1/tickets/tickets/${ticketId}/comments/`
|
||||||
|
if (!commentText) { return }
|
||||||
|
const body = {
|
||||||
|
body: commentText,
|
||||||
|
ticket: ticketId
|
||||||
|
}
|
||||||
|
this.$axios.post(commentUrl, body).then(res => {
|
||||||
|
if (successCallback) {
|
||||||
|
successCallback()
|
||||||
|
} else {
|
||||||
|
this.reloadPage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleApprove() {
|
||||||
|
// this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||||
|
if (this.request_form.asset.length === 0 || this.request_form.systemuser === '') {
|
||||||
|
return this.$message.error(this.$t('common.NeedAssetsAndSystemUserErrMsg'))
|
||||||
|
} else {
|
||||||
|
this.$axios.patch(`/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/`, {
|
||||||
|
confirmed_system_user: this.request_form.systemuser,
|
||||||
|
confirmed_assets: this.request_form.asset
|
||||||
|
}).then(res => {
|
||||||
|
this.$axios.post(`/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/approve/`).then(
|
||||||
|
() => {
|
||||||
|
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||||
|
this.reloadPage()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}).catch(
|
||||||
|
() => this.$message.success(this.$t('common.updateErrorMsg'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleReject() {
|
||||||
|
this.createComment(function() {})
|
||||||
|
const url = `/api/v1/tickets/tickets/${this.object.id}/`
|
||||||
|
const data = { action: 'reject' }
|
||||||
|
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||||
|
},
|
||||||
|
handleClosed() {
|
||||||
|
const url = `/api/v1/tickets/tickets/${this.object.id}/`
|
||||||
|
const data = { status: 'closed' }
|
||||||
|
this.$axios.patch(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||||
|
},
|
||||||
|
handleComment() {
|
||||||
|
this.createComment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 2.5;
|
||||||
|
}
|
||||||
|
.assets{
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
.feed-activity-list {
|
||||||
|
padding-top: 20px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.feed-activity-list .feed-element {
|
||||||
|
border-bottom: 1px solid #e7eaec;
|
||||||
|
}
|
||||||
|
.feed-element:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.feed-element {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
.feed-element,
|
||||||
|
.media-body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.feed-element > .pull-left {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.feed-element .header-avatar {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
.text-muted {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
.el-button--mini {
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
</style>
|
46
src/views/tickets/RequestAssetPerm/Detail/index.vue
Normal file
46
src/views/tickets/RequestAssetPerm/Detail/index.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<GenericDetailPage :object.sync="ticket" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
|
||||||
|
<component :is="config.activeMenu" :object="ticket" />
|
||||||
|
</GenericDetailPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { GenericDetailPage, TabPage } from '@/layout/components'
|
||||||
|
import TicketDetail from './TicketDetail'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GenericDetailPage,
|
||||||
|
TicketDetail,
|
||||||
|
TabPage
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ticket: { title: '', user_display: '', type_display: '', status: '', assignees_display: '', date_created: '' },
|
||||||
|
config: {
|
||||||
|
activeMenu: 'TicketDetail',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
title: this.$t('route.TicketDetail'),
|
||||||
|
name: 'TicketDetail'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
getObjectName: this.getObjectName,
|
||||||
|
hasRightSide: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getObjectName() {
|
||||||
|
return this.ticket.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<GenericCreateUpdatePage v-bind="$data" :perform-submit="performSubmit" :create-success-next-route="createSuccessNextRoute" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||||
|
import Select2 from '@/components/Select2'
|
||||||
|
import { getDaysFuture } from '@/utils/common'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GenericCreateUpdatePage
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const now = new Date()
|
||||||
|
const date_expired = getDaysFuture(7, now).toISOString()
|
||||||
|
const date_start = now.toISOString()
|
||||||
|
return {
|
||||||
|
initial: {
|
||||||
|
ips_or_not: true,
|
||||||
|
date_expired: date_expired,
|
||||||
|
date_start: date_start,
|
||||||
|
org_id: 'DEFAULT'
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
[this.$t('common.Basic'), ['title', 'ips', 'hostname', 'date_start', 'date_expired', 'org_id', 'assignees']]
|
||||||
|
],
|
||||||
|
fieldsMeta: {
|
||||||
|
ips: {
|
||||||
|
helpText: '请输入逗号分割的IP地址组'
|
||||||
|
},
|
||||||
|
hostname: {
|
||||||
|
helpText: '支持模糊匹配'
|
||||||
|
},
|
||||||
|
org_id: {
|
||||||
|
component: Select2,
|
||||||
|
el: {
|
||||||
|
multiple: false,
|
||||||
|
options: this.$store.state.users.profile.user_all_orgs
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
changeOptions: ([event], updateForm) => {
|
||||||
|
console.log(event[0].value) // output: input value
|
||||||
|
this.fieldsMeta.assignees.el.ajax.url = `/api/v1/tickets/tickets/request-asset-perm/assignees/?org_id=${event[0].value}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assignees: {
|
||||||
|
el: {
|
||||||
|
multiple: true,
|
||||||
|
value: [],
|
||||||
|
ajax: {
|
||||||
|
url: '/api/v1/tickets/tickets/request-asset-perm/assignees/',
|
||||||
|
transformOption: (item) => {
|
||||||
|
return { label: item.name + '(' + item.username + ')', value: item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url: '/api/v1/tickets/tickets/request-asset-perm/',
|
||||||
|
createSuccessNextRoute: {
|
||||||
|
name: 'TicketList'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
performSubmit(validValues) {
|
||||||
|
const ips = validValues.ips
|
||||||
|
if (ips) {
|
||||||
|
validValues.ips = ips.split(',')
|
||||||
|
}
|
||||||
|
return this.$axios['post'](this.url, validValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
@@ -1,32 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row>
|
<el-row>
|
||||||
<DetailCard :title="cardTitle" :items="detailCardItems">
|
<el-col :span="18">
|
||||||
<div class="feed-activity-list">
|
<DetailCard :title="cardTitle" :items="detailCardItems">
|
||||||
<div class="feed-element">
|
<div class="feed-activity-list">
|
||||||
<a href="#" class="pull-left">
|
<div class="feed-element">
|
||||||
<el-avatar :src="imageUrl" class="header-avatar" />
|
<a href="#" class="pull-left">
|
||||||
</a>
|
<el-avatar :src="imageUrl" class="header-avatar" />
|
||||||
<div class="media-body ">
|
</a>
|
||||||
<strong>{{ object.user_display }}</strong> <small class="text-muted"> {{ formatTime(object.date_created) }}</small>
|
<div class="media-body ">
|
||||||
<br>
|
<strong>{{ object.user_display }}</strong> <small class="text-muted"> {{ formatTime(object.date_created) }}</small>
|
||||||
<small class="text-muted">{{ toSafeLocalDateStr(object.date_created) }} </small>
|
<br>
|
||||||
<div style="padding-top: 10px">
|
<small class="text-muted">{{ toSafeLocalDateStr(object.date_created) }} </small>
|
||||||
<span v-html="object.body" />
|
<div style="padding-top: 10px">
|
||||||
|
<span v-html="object.body" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<template v-if="comments">
|
||||||
<template v-if="comments">
|
<div v-for="item in comments" :key="item.user_display + item.body">
|
||||||
<div v-for="item in comments" :key="item.user_display + item.body">
|
<div class="feed-element">
|
||||||
<div class="feed-element">
|
<a href="#" class="pull-left">
|
||||||
<a href="#" class="pull-left">
|
<el-avatar :src="imageUrl" class="header-avatar" />
|
||||||
<el-avatar :src="imageUrl" class="header-avatar" />
|
</a>
|
||||||
</a>
|
<div class="media-body ">
|
||||||
<div class="media-body ">
|
<strong>{{ item.user_display }}</strong> <small class="text-muted">{{ formatTime(item.date_created) }}</small>
|
||||||
<strong>{{ item.user_display }}</strong> <small class="text-muted">{{ formatTime(item.date_created) }}</small>
|
<br>
|
||||||
<br>
|
<small class="text-muted">{{ toSafeLocalDateStr(item.date_created) }}</small>
|
||||||
<small class="text-muted">{{ toSafeLocalDateStr(item.date_created) }}</small>
|
<div style="padding-top: 10px">
|
||||||
<div style="padding-top: 10px">
|
{{ item.body }}
|
||||||
{{ item.body }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +71,8 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
statusMap: this.object.status === 'open' ? STATUS_MAP[this.object.status] : STATUS_MAP[this.object.action],
|
|
||||||
|
: this.object.status === 'open' ? STATUS_MAP[this.object.status] : STATUS_MAP[this.object.action],
|
||||||
imageUrl: require('@/assets/img/admin.png'),
|
imageUrl: require('@/assets/img/admin.png'),
|
||||||
form: {
|
form: {
|
||||||
comments: ''
|
comments: ''
|
||||||
|
@@ -32,7 +32,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getObjectName() {
|
getObjectName() {
|
||||||
return this.ticket.user_display
|
return this.ticket.title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,10 @@ export default {
|
|||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '/api/v1/tickets/tickets/'
|
default: '/api/v1/tickets/tickets/'
|
||||||
|
},
|
||||||
|
hasMoreActions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -28,7 +32,13 @@ export default {
|
|||||||
formatter: DetailFormatter,
|
formatter: DetailFormatter,
|
||||||
sortable: 'custom',
|
sortable: 'custom',
|
||||||
formatterArgs: {
|
formatterArgs: {
|
||||||
route: 'TicketDetail'
|
getRoute: function({ row }) {
|
||||||
|
if (row.type === 'request_asset') {
|
||||||
|
return 'AssetsTicketDetail'
|
||||||
|
} else {
|
||||||
|
return 'TicketDetail'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -38,8 +48,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'type_display',
|
prop: 'type_display',
|
||||||
label: this.$t('tickets.type'),
|
label: this.$t('tickets.type')
|
||||||
sortable: 'custom',
|
|
||||||
width: '110px'
|
width: '110px'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -65,8 +74,10 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
ticketActions: {
|
ticketActions: {
|
||||||
hasLeftActions: false,
|
hasLeftActions: this.hasMoreActions,
|
||||||
hasRightActions: false,
|
hasRightActions: false,
|
||||||
|
hasCreate: false,
|
||||||
|
hasBulkDelete: false,
|
||||||
searchConfig: {
|
searchConfig: {
|
||||||
default: {
|
default: {
|
||||||
status: {
|
status: {
|
||||||
@@ -76,9 +87,28 @@ export default {
|
|||||||
valueLabel: this.$t('tickets.Open')
|
valueLabel: this.$t('tickets.Open')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
moreActionsTitle: this.$t('common.RequestTickets'),
|
||||||
|
moreActionsType: 'primary',
|
||||||
|
extraMoreActions: this.genExtraMoreActions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
genExtraMoreActions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
title: this.$t('tickets.RequestAssetPerm'),
|
||||||
|
type: 'primary',
|
||||||
|
can: true,
|
||||||
|
callback: this.onCallback
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
onCallback() {
|
||||||
|
this.$router.push({ name: 'RequestAssetPermTicketCreateUpdate' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Reference in New Issue
Block a user