feat: 重构申请工单系统

This commit is contained in:
Orange
2021-01-08 16:42:04 +08:00
parent 5acbdd5679
commit fdeab46970
13 changed files with 364 additions and 66 deletions

View File

@@ -203,6 +203,7 @@
"Basic": "基本",
"PleaseAgreeToTheTerms": "请同意条款",
"BasicInfo": "基本信息",
"ApplyInfo": "申请信息",
"Cancel": "取消",
"Close": "关闭",
"Command filter": "命令过滤器",
@@ -803,6 +804,8 @@
"reply": "回复",
"status": "状态",
"title": "标题",
"action": "动作",
"IPGroup": "IP 组",
"type": "类型",
"user": "用户",
"Status": "状态",
@@ -815,6 +818,7 @@
"Asset": "资产",
"SystemUser": "系统用户",
"RequestAssetPerm": "申请资产授权",
"RequestApplicationPerm": "申请应用授权",
"Applicant": "申请人",
"Pending": "待处理",
"Approved": "已同意",
@@ -822,7 +826,8 @@
"Closed": "已完成",
"helpText": {
"ips": "请输入逗号分割的IP地址组",
"fuzzySearch": "支持模糊搜索"
"fuzzySearch": "支持模糊搜索",
"application": "请输入逗号分割的应用组"
}
},
"tree": {

View File

@@ -203,6 +203,7 @@
"BadConflictErrorMsg": "Refreshing, please try again later",
"Basic": "Basic",
"BasicInfo": "Basic info",
"ApplyInfo": "Apply info",
"Cancel": "Cancel",
"Close": "Close",
"Command filter": "Command filter",
@@ -789,6 +790,8 @@
"Close": "Close",
"Comment": "Comment",
"MyTickets": "My tickets",
"action": "Action",
"IPGroup": "IP 组",
"Reject": "Reject",
"date": "Date",
"reply": "Reply",
@@ -804,13 +807,15 @@
"SystemUser": "System user",
"Applicant": "Applicant",
"RequestAssetPerm": "Request asset perm",
"RequestApplicationPerm": "Request application perm",
"Pending": "Open",
"Approved": "Approved",
"Rejected": "Rejected",
"Closed": "Closed",
"helpText": {
"ips": "Enter the IP address group, separated by commas",
"fuzzySearch": "Support for fuzzy search"
"fuzzySearch": "Support for fuzzy search",
"application": "Enter the application group, separated by commas"
}
},
"tree": {

View File

@@ -27,5 +27,12 @@ export default [
component: () => import('@/views/tickets/RequestAssetPerm/Detail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
hidden: true
},
{
path: 'tickets/request-application-perm/create',
name: 'RequestApplicationPermTicketCreateUpdate',
component: () => import('@/views/tickets/RequestApplicationPerm/RequestApplicationPermTicketCreateUpdate'),
meta: { title: i18n.t('route.TicketCreate'), activeMenu: '/tickets/tickets' },
hidden: true
}
]

View File

@@ -0,0 +1,188 @@
<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'
import { Required } from '@/components/DataForm/rules'
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,
apply_date_expired: date_expired,
apply_date_start: date_start,
org_id: 'DEFAULT',
type: 'apply_application',
apply_actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file']
},
fields: [
[this.$t('common.Basic'), ['title', 'type', 'org_id', 'assignees', 'comment']],
[this.$t('tickets.RequestPerm'), ['apply_category_type', 'apply_application_group', 'apply_system_user_group', 'apply_date_start', 'apply_date_expired']]
],
fieldsMeta: {
type: {
el: {
disabled: true
}
},
apply_category_type: {
type: 'cascader',
label: this.$t('applications.appType'),
rules: [Required],
el: {
multiple: false,
options: [
{ label: 'DB',
value: 'db',
children: [
{
label: 'MySQL',
value: 'mysql'
},
{
label: 'Oracle',
value: 'oracle'
},
{
label: 'PostgreSQL',
value: 'postgresql'
},
{
label: 'MariaDB',
value: 'mariadb'
}
]
},
{ label: 'Cloud', value: 'cloud', children: [
{
label: 'Kubernetes',
value: 'k8s'
}
]
},
{ label: 'Remote App', value: 'remote_app',
children: [
{
label: 'MySQL Workbench',
value: 'mysql_workbench'
},
{
label: 'vSphere Client',
value: 'vmware_client'
},
{
label: 'Custom',
value: 'custom'
}, {
label: 'Chrome',
value: 'chrome'
}
]
}
]
}
},
apply_application_group: {
label: this.$t('assets.Applications'),
helpText: this.$t('tickets.helpText.application')
},
apply_system_user_group: {
label: this.$t('assets.SystemUser'),
helpText: this.$t('tickets.helpText.fuzzySearch')
},
apply_date_start: {
label: this.$t('common.DateStart'),
type: 'date-picker',
el: {
type: 'datetime'
}
},
apply_date_expired: {
label: this.$t('common.DateEnd'),
type: 'date-picker',
el: {
type: 'datetime'
}
},
org_id: {
component: Select2,
el: {
multiple: false,
options: this.$store.state.users.profile.user_all_orgs
},
on: {
changeOptions: ([event], updateForm) => {
this.fieldsMeta.assignees.el.ajax.url = `/api/v1/tickets/assignees/?org_id=${event[0].value}`
}
}
},
assignees: {
el: {
multiple: true,
value: [],
ajax: {
url: `/api/v1/tickets/assignees/?org_id=DEFAULT`,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
url: '/api/v1/tickets/tickets/?type=apply_application&action=open',
createSuccessNextRoute: {
name: 'TicketList'
}
}
},
methods: {
performSubmit(validValues) {
console.log(validValues)
const meta = {}
const applications = validValues.apply_application_group
if (applications) {
validValues.meta.apply_application_group = applications.split(',')
}
if (applications === '') {
delete validValues['apply_application_group']
}
if (validValues.apply_system_user_group) {
meta.apply_system_user_group = validValues.apply_system_user_group
delete validValues['apply_system_user_group']
}
meta.apply_category = validValues.apply_category_type[0]
meta.apply_type = validValues.apply_category_type[1]
meta.apply_date_start = validValues.apply_date_start
delete validValues['apply_date_start']
meta.apply_date_expired = validValues.apply_date_expired
delete validValues['apply_date_expired']
validValues['meta'] = meta
return this.$axios['post'](`/api/v1/tickets/tickets/open/?type=apply_application`, validValues)
}
}
}
</script>
<style lang="less" scoped>
.el-form ::v-deep .el-cascader{
width: 100%;
}
</style>

View File

@@ -19,6 +19,18 @@
<el-form-item :label="$t('tickets.SystemUser')" required>
<Select2 v-model="requestForm.systemuser" v-bind="systemuser_select2" style="width: 50% !important" />
</el-form-item>
<el-form-item :label="$t('common.dateStart')" required>
<el-date-picker
v-model="requestForm.apply_date_start"
type="datetime"
/>
</el-form-item>
<el-form-item :label="$t('common.dateExpired')" required>
<el-date-picker
v-model="requestForm.apply_date_expired"
type="datetime"
/>
</el-form-item>
<el-form-item :label="$t('assets.Action')" required>
<AssetPermissionFormActionField v-model="requestForm.actions" style="width: 30% !important" />
</el-form-item>
@@ -49,17 +61,19 @@ export default {
return {
statusMap: this.object.status === 'open' ? STATUS_MAP[this.object.status] : STATUS_MAP[this.object.action],
requestForm: {
asset: this.object.confirmed_assets,
systemuser: this.object.confirmed_system_users,
actions: this.object.actions
asset: this.object.meta.recommend_assets,
systemuser: this.object.meta.recommend_system_users,
actions: this.object.meta.apply_actions,
apply_date_expired: this.object.meta.apply_date_expired,
apply_date_start: this.object.meta.apply_date_start
},
comments: '',
assets: [],
asset_select2: {
multiple: true,
value: this.object.confirmed_assets,
value: this.object.meta.recommend_assets,
ajax: {
url: this.object.assets_waitlist_url,
url: `/api/v1/assets/assets/?org_id=${(this.object.org_id === '') ? 'DEFAULT' : this.object.org_id}&protocol__in=rdp,vnc,ssh,telnet`,
transformOption: (item) => {
return { label: item.hostname, value: item.id }
}
@@ -67,9 +81,9 @@ export default {
},
systemuser_select2: {
multiple: true,
value: this.object.confirmed_system_users,
value: this.object.meta.recommend_system_users,
ajax: {
url: this.object.system_users_waitlist_url,
url: `/api/v1/assets/system-users/?org_id=${(this.object.org_id === '') ? 'DEFAULT' : this.object.org_id}&protocol__in=rdp,vnc,ssh,telnet`,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
@@ -93,7 +107,7 @@ export default {
},
{
key: this.$t('tickets.user'),
value: this.object.user_display
value: this.object.applicant_display
},
{
key: this.$t('tickets.Assignees'),
@@ -101,7 +115,7 @@ export default {
},
{
key: this.$t('tickets.Assignee'),
value: this.object.assignee_display
value: this.object.processor
},
{
key: this.$t('common.dateCreated'),
@@ -121,23 +135,23 @@ export default {
// },
{
key: this.$t('tickets.IP'),
value: this.object.ips
value: this.object.meta.apply_ip_group
},
{
key: this.$t('tickets.Hostname'),
value: this.object.hostname
value: this.object.meta.apply_hostname_group
},
{
key: this.$t('tickets.SystemUser'),
value: this.object.system_user
value: this.object.meta.apply_system_user_group
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.date_start)
value: toSafeLocalDateStr(this.object.meta.apply_date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.date_expired)
value: toSafeLocalDateStr(this.object.meta.apply_date_expired)
}
]
},
@@ -159,31 +173,31 @@ export default {
if (this.requestForm.asset.length === 0 || this.requestForm.systemuser.length === 0) {
return this.$message.error(this.$t('common.NeedAssetsAndSystemUserErrMsg'))
} else {
this.$axios.patch(`/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/`, {
confirmed_system_users: this.requestForm.systemuser,
confirmed_assets: this.requestForm.asset,
actions: this.requestForm.actions
}).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.$axios.put(`/api/v1/tickets/tickets/${this.object.id}/approve/`, {
meta: {
approve_system_users: this.requestForm.systemuser,
approve_assets: this.requestForm.asset,
approve_actions: this.requestForm.actions,
approve_date_start: this.requestForm.apply_date_start,
approve_date_expired: this.requestForm.apply_date_expired
}
}).then(
() => {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.reloadPage()
}
).catch(
() => this.$message.success(this.$t('common.updateErrorMsg'))
)
}
},
handleClose() {
const url = `/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/close/`
const data = { status: 'closed' }
this.$axios.post(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
const url = `/api/v1/tickets/tickets/${this.object.id}/close/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
},
handleReject() {
const url = `/api/v1/tickets/tickets/request-asset-perm/${this.object.id}/reject/`
const data = { action: 'reject' }
this.$axios.post(url, data).then(res => this.reloadPage()).catch(err => this.$message.error(err))
const url = `/api/v1/tickets/tickets/${this.object.id}/reject/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
}
}
}

View File

@@ -19,12 +19,16 @@ export default {
ticket: { title: '', user_display: '', type_display: '', status: '', assignees_display: '', date_created: '' },
config: {
activeMenu: 'TicketDetail',
url: '',
submenu: [
{
title: this.$t('route.TicketDetail'),
name: 'TicketDetail'
}
],
actions: {
detailApiUrl: `/api/v1/tickets/tickets/${this.$route.params.id}/`
},
getObjectName: this.getObjectName,
hasRightSide: false
}

View File

@@ -21,31 +21,54 @@ export default {
hasDetailInMsg: false,
initial: {
ips_or_not: true,
date_expired: date_expired,
date_start: date_start,
apply_date_expired: date_expired,
apply_date_start: date_start,
org_id: 'DEFAULT',
actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file']
type: 'apply_asset',
apply_actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file']
},
fields: [
[this.$t('common.Basic'), ['title', 'org_id', 'assignees', 'comment']],
[this.$t('tickets.RequestPerm'), ['ips', 'hostname', 'system_user', 'actions', 'date_start', 'date_expired']]
[this.$t('common.Basic'), ['title', 'type', 'org_id', 'assignees', 'comment']],
[this.$t('tickets.RequestPerm'), ['apply_ip_group', 'apply_hostname_group', 'apply_system_user_group', 'apply_actions', 'apply_date_start', 'apply_date_expired']]
],
fieldsMeta: {
ips: {
type: {
el: {
disabled: true
}
},
apply_ip_group: {
label: this.$t('tickets.IPGroup'),
helpText: this.$t('tickets.helpText.ips')
},
hostname: {
apply_hostname_group: {
label: this.$t('assets.Hostname'),
helpText: this.$t('tickets.helpText.fuzzySearch')
},
system_user: {
apply_system_user_group: {
label: this.$t('assets.SystemUser'),
helpText: this.$t('tickets.helpText.fuzzySearch')
},
actions: {
apply_actions: {
label: this.$t('perms.Actions'),
component: AssetPermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
apply_date_start: {
label: this.$t('common.DateStart'),
type: 'date-picker',
el: {
type: 'datetime'
}
},
apply_date_expired: {
label: this.$t('common.DateEnd'),
type: 'date-picker',
el: {
type: 'datetime'
}
},
org_id: {
component: Select2,
el: {
@@ -54,8 +77,7 @@ export default {
},
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}`
this.fieldsMeta.assignees.el.ajax.url = `/api/v1/tickets/assignees/?org_id=${event[0].value}`
}
}
},
@@ -64,7 +86,7 @@ export default {
multiple: true,
value: [],
ajax: {
url: '/api/v1/tickets/tickets/request-asset-perm/assignees/',
url: `/api/v1/tickets/assignees/?org_id=DEFAULT`,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
@@ -72,7 +94,7 @@ export default {
}
}
},
url: '/api/v1/tickets/tickets/request-asset-perm/',
url: '/api/v1/tickets/tickets/?type=apply_asset&action=open',
createSuccessNextRoute: {
name: 'TicketList'
}
@@ -80,14 +102,37 @@ export default {
},
methods: {
performSubmit(validValues) {
const ips = validValues.ips
const meta = {}
const ips = validValues.apply_ip_group
if (ips) {
validValues.ips = ips.split(',')
validValues.meta.apply_ip_group = ips.split(',')
}
if (ips === '') {
delete validValues['ips']
delete validValues['apply_ip_group']
}
return this.$axios['post'](this.url, validValues)
if (validValues.apply_hostname_group) {
meta.apply_hostname_group = validValues.apply_ip_group
delete validValues['apply_hostname_group']
}
if (validValues.apply_system_user_group) {
meta.apply_system_user_group = validValues.apply_system_user_group
delete validValues['apply_system_user_group']
}
if (validValues.apply_actions) {
meta.apply_actions = validValues.apply_actions
delete validValues['apply_actions']
}
meta.apply_date_start = validValues.apply_date_start
delete validValues['apply_date_start']
meta.apply_date_expired = validValues.apply_date_expired
delete validValues['apply_date_expired']
validValues['meta'] = meta
return this.$axios['post'](`/api/v1/tickets/tickets/open/?type=apply_asset`, validValues)
}
}
}

View File

@@ -16,7 +16,14 @@ export default {
},
data() {
return {
ticket: { title: '', user_display: '', type_display: '', status: '', assignees_display: '', date_created: '' },
ticket: {
title: '',
user_display: '',
type_display: '',
status: '',
assignees_display: '',
date_created: ''
},
config: {
activeMenu: 'TicketDetail',
submenu: [

View File

@@ -32,7 +32,9 @@ export default {
sortable: 'custom',
formatterArgs: {
getRoute: function({ row }) {
if (row.type === 'request_asset') {
if (row.type === 'apply_asset') {
return 'AssetsTicketDetail'
} else if (row.type === 'apply_application') {
return 'AssetsTicketDetail'
} else {
return 'TicketDetail'
@@ -48,7 +50,7 @@ export default {
{
prop: 'type_display',
label: this.$t('tickets.type'),
width: '110px'
width: '160px'
},
{
prop: 'status',
@@ -56,6 +58,20 @@ export default {
align: 'center',
width: '90px',
sortable: 'custom',
formatter: row => {
if (row.status === 'open') {
return <el-tag type='primary' size='mini'style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Open') }</el-tag>
} else {
return <el-tag type='danger' size='mini'style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Closed') }</el-tag>
}
}
},
{
prop: 'action',
label: this.$t('tickets.action'),
align: 'center',
width: '90px',
sortable: 'custom',
formatter: row => {
if (row.status === 'open') {
return <el-tag type='success' size='mini'style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Pending') }</el-tag>
@@ -69,7 +85,6 @@ export default {
return <el-tag type='info' size='mini' style='align-items:center; display: flex; justify-content:center;'> { this.$t('tickets.Closed') }</el-tag>
}
}
},
{
prop: 'date_created',
@@ -105,16 +120,20 @@ export default {
genExtraMoreActions() {
return [
{
name: '',
name: 'RequestAssetPerm',
title: this.$t('tickets.RequestAssetPerm'),
type: 'primary',
can: true,
callback: this.onCallback
callback: () => this.$router.push({ name: 'RequestAssetPermTicketCreateUpdate' })
},
{
name: 'RequestApplicationPerm',
title: this.$t('tickets.RequestApplicationPerm'),
type: 'primary',
can: true,
callback: () => this.$router.push({ name: 'RequestApplicationPermTicketCreateUpdate' })
}
]
},
onCallback() {
this.$router.push({ name: 'RequestAssetPermTicketCreateUpdate' })
}
}
}

View File

@@ -117,7 +117,7 @@ export default {
},
getComment() {
this.loading = true
const url = `/api/v1/tickets/tickets/${this.object.id}/comments/`
const url = `/api/v1/tickets/comments/?ticket_id=${this.object.id}`
this.$axios.get(url).then(res => {
this.comments = res
}).catch(err => {
@@ -148,7 +148,7 @@ export default {
createComment(successCallback) {
const commentText = this.form.comments
const ticketId = this.object.id
const commentUrl = `/api/v1/tickets/tickets/${ticketId}/comments/`
const commentUrl = `/api/v1/tickets/comments/?ticket_id=${this.object.id}`
if (!commentText) { return }
const body = {
body: commentText,

View File

@@ -1,7 +1,7 @@
<template>
<IBox class="box">
<div slot="header" class="clearfix ibox-title">
<i class="fa fa-info-circle" /> {{ $t('common.BasicInfo') }}
<i class="fa fa-info-circle" /> {{ title }}
</div>
<div class="content">
<el-row :gutter="10">
@@ -54,6 +54,9 @@ export default {
detailCardItems: {
type: Array,
default: () => ([])
},
title: {
type: String
}
},
data() {

View File

@@ -1,7 +1,8 @@
<template>
<el-row>
<el-col :span="17">
<Details :detail-card-items="detailCardItems" :special-card-items="specialCardItems" />
<Details :detail-card-items="detailCardItems" :title="$t('common.BasicInfo')" />
<Details :detail-card-items="specialCardItems" :title="$t('common.ApplyInfo')" />
<slot id="MoreDetails" />
<Comments :object="object" v-bind="$attrs" />
</el-col>

View File

@@ -7,7 +7,7 @@
:description="`${this.$t('tickets.Applicant')}${object.user_display}`"
>
<div slot="description">
<div>{{ `${this.$t('tickets.Applicant')}${object.user_display}` }}</div>
<div>{{ `${this.$t('tickets.Applicant')}${object.applicant_display}` }}</div>
<div>{{ `${this.$t('common.dateCreated')}: ${toSafeLocalDateStr(object.date_created)}` }}</div>
</div>
</el-step>
@@ -20,7 +20,7 @@
:description="ticketSteps===STATUS.close ? `${this.$t('tickets.Assignee')}${object.assignee_display}`:'' "
>
<div v-if="ticketSteps===STATUS.close" slot="description">
<div>{{ `${this.$t('tickets.Assignee')}${object.assignee_display}` }}</div>
<div>{{ `${this.$t('tickets.Assignee')}${object.processor_display}` }}</div>
<div>{{ `${this.$t('common.dateFinished')}: ${toSafeLocalDateStr(object.date_updated)}` }}</div>
</div>
</el-step>