前端更新 (#189)

* fix:add package

* fix:notice_info为null的情况

* fix:2 bugs

* feat:1.common增加通知配置 2.cmdb预定义值webhook&其他模型

* fix:json 不支持预定义值

* fix:json 不支持预定义值
This commit is contained in:
wang-liang0615
2023-10-09 17:43:34 +08:00
committed by GitHub
parent 8c6389b4f8
commit d501436e3d
29 changed files with 4835 additions and 3197 deletions

View File

@@ -83,10 +83,10 @@ export default {
getContent() {
const html = _.cloneDeep(this.editor.getHtml())
const _html = html.replace(
/<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline.*?<\/span>/gm,
/<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm,
(value) => {
const _match = value.match(/(?<=data-attachmentValue=").*?(?=")/)
return `{{${_match}}}`
const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/)
return `{{${_match[0]}}}`
}
)
return { body_html: html, body: _html }

View File

@@ -59,7 +59,7 @@ export default {
]
return {
segmentedContentTypes,
// contentType: 'none',
// contentType: 'none',
jsonData: {},
}
},
@@ -74,6 +74,9 @@ export default {
}
div.jsoneditor {
border-color: #f3f4f6;
.jsoneditor-outer {
border-color: #f3f4f6;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,195 +1,362 @@
<template>
<a-tabs id="preValueArea" v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }">
<a-tab-pane key="define" :disabled="disabled">
<span style="font-size:12px;" slot="tab">定义</span>
<PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled">
<template #default>
<a-button
:style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }"
type="primary"
ghost
:disabled="disabled"
size="small"
>
<a-icon type="plus" />添加</a-button
>
</template>
</PreValueTag>
<draggable :list="valueList" handle=".handle" :disabled="disabled">
<PreValueTag
:disabled="disabled"
v-for="(item, index) in valueList"
:key="`${item[0]}_${index}`"
:item="item"
@deleteValue="deleteValue"
@editValue="editValue"
/>
</draggable>
</a-tab-pane>
<a-tab-pane key="webhook" :disabled="disabled">
<span style="font-size:12px;" slot="tab">Webhook</span>
<a-form-model :model="form">
<a-row :gutter="24">
<a-col :span="24">
<a-form-model-item label="地址" prop="url" :labelCol="{ span: 3 }" :wrapperCol="{ span: 16 }">
<a-input v-model="form.url" :disabled="disabled">
<a-select
:showArrow="false"
slot="addonBefore"
style="width:60px;"
v-model="form.method"
:disabled="disabled"
>
<a-select-option value="get">
GET
</a-select-option>
<a-select-option value="post">
POST
</a-select-option>
<a-select-option value="put">
PUT
</a-select-option>
</a-select>
</a-input>
</a-form-model-item>
</a-col>
</a-row>
<a-col :span="24">
<a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }">
<template slot="label">
<span
style="position:relative;white-space:pre;"
>{{ `过滤` }}
<a-tooltip
title="返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]"
>
<a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
/>
</a-tooltip>
</span>
</template>
<a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" />
</a-form-model-item>
</a-col>
</a-form-model>
</a-tab-pane>
</a-tabs>
</template>
<script>
import _ from 'lodash'
import draggable from 'vuedraggable'
import PreValueTag from './preValueTag.vue'
import { defautValueColor } from '../../utils/const'
import ColorPicker from '../../components/colorPicker/index.vue'
export default {
name: 'PreValueArea',
components: { draggable, PreValueTag, ColorPicker },
props: {
disabled: {
type: Boolean,
default: true,
},
},
data() {
return {
defautValueColor,
activeKey: 'define', // define webhook
valueList: [],
form: {
url: '',
method: 'get',
ret_key: '',
},
}
},
watch: {
disabled: {
immediate: false,
handler(newValue) {
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (newValue) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
},
},
methods: {
addNewValue(newValue, newStyle, newIcon) {
if (newValue) {
const idx = this.valueList.findIndex((v) => v[0] === newValue)
if (idx > -1) {
this.$message.warning('当前值已存在!')
} else {
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }])
}
}
},
deleteValue(item) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList.splice(idx, 1)
this.valueList = _valueList
}
},
editValue(item, newValue, newStyle, newIcon) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }]
this.valueList = _valueList
}
},
getData() {
if (this.activeKey === 'define') {
return {
choice_value: this.valueList,
choice_web_hook: null,
}
} else {
return { choice_value: [], choice_web_hook: this.form }
}
},
setData({ choice_value, choice_web_hook }) {
if (choice_web_hook) {
this.form = choice_web_hook
this.activeKey = 'webhook'
} else {
this.valueList = choice_value
this.activeKey = 'define'
}
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (this.disabled) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
},
}
</script>
<style lang="less" scoped>
.pre-value-edit-color {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
.pre-value-edit-color-item {
cursor: pointer;
display: inline-block;
width: 25px;
height: 20px;
margin: 5px;
}
}
</style>
<template>
<a-tabs id="preValueArea" v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }">
<a-tab-pane key="define" :disabled="disabled">
<span style="font-size:12px;" slot="tab">定义</span>
<PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled">
<template #default>
<a-button
:style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }"
type="primary"
ghost
:disabled="disabled"
size="small"
>
<a-icon type="plus" />添加</a-button
>
</template>
</PreValueTag>
<draggable :list="valueList" handle=".handle" :disabled="disabled">
<PreValueTag
:disabled="disabled"
v-for="(item, index) in valueList"
:key="`${item[0]}_${index}`"
:item="item"
@deleteValue="deleteValue"
@editValue="editValue"
/>
</draggable>
</a-tab-pane>
<a-tab-pane key="webhook" :disabled="disabled">
<span style="font-size:12px;" slot="tab">Webhook</span>
<Webhook ref="webhook" style="margin-top:10px" />
<a-form-model :model="form">
<a-col :span="24">
<a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }">
<template slot="label">
<span
style="position:relative;white-space:pre;"
>{{ `过滤` }}
<a-tooltip
title="返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]"
>
<a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
/>
</a-tooltip>
</span>
</template>
<a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" />
</a-form-model-item>
</a-col>
</a-form-model>
</a-tab-pane>
<a-tab-pane key="choice_other" :disabled="disabled">
<span style="font-size:12px;" slot="tab">其他模型属性</span>
<a-row :gutter="[24, 24]">
<a-col :span="12">
<a-form-item
:style="{ lineHeight: '24px', marginBottom: '5px' }"
label="模型"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<treeselect
:disable-branch-nodes="true"
:class="{
'custom-treeselect': true,
'custom-treeselect-bgcAndBorder': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '14px',
}"
v-model="choice_other.type_ids"
:multiple="true"
:clearable="true"
searchable
:options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择CMDB模型"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
children: node.ci_types,
}
}
"
appendToBody
:zIndex="1050"
@select="
() => {
choice_other.attr_id = undefined
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</a-form-item>
</a-col>
<a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item
:style="{ marginBottom: '5px' }"
label="属性"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<treeselect
:disable-branch-nodes="true"
class="ops-setting-treeselect"
v-model="choice_other.attr_id"
:multiple="false"
:clearable="true"
searchable
:options="typeAttrs"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择模型属性"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
}
}
"
appendToBody
:zIndex="1050"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</a-form-item>
</a-col>
<a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item
:style="{ marginBottom: '5px' }"
class="pre-value-filter"
label="筛选"
:label-col="{ span: 2 }"
:wrapper-col="{ span: 22 }"
>
<FilterComp
ref="filterComp"
:isDropdown="false"
:canSearchPreferenceAttrList="typeAttrs"
@setExpFromFilter="setExpFromFilter"
:expression="filterExp ? `q=${filterExp}` : ''"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</template>
<script>
import _ from 'lodash'
import draggable from 'vuedraggable'
import PreValueTag from './preValueTag.vue'
import { defautValueColor } from '../../utils/const'
import ColorPicker from '../../components/colorPicker/index.vue'
import Webhook from '../../components/webhook'
import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr'
import FilterComp from '@/components/CMDBFilterComp'
export default {
name: 'PreValueArea',
components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp },
props: {
disabled: {
type: Boolean,
default: true,
},
},
data() {
return {
defautValueColor,
activeKey: 'define', // define webhook
valueList: [],
form: {
ret_key: '',
},
choice_other: {
type_ids: undefined,
attr_id: undefined,
},
ciTypeGroup: [],
typeAttrs: [],
filterExp: '',
}
},
watch: {
disabled: {
immediate: false,
handler(newValue) {
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (newValue) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
},
'choice_other.type_ids': {
handler(newValue) {
if (newValue && newValue.length) {
getCITypeCommonAttributesByTypeIds({ type_ids: newValue.join(',') }).then((res) => {
this.typeAttrs = res.attributes
})
}
},
},
},
created() {
getCITypeGroups({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `parent_${item.id || -1}`
return { ..._.cloneDeep(item) }
})
})
},
methods: {
addNewValue(newValue, newStyle, newIcon) {
if (newValue) {
const idx = this.valueList.findIndex((v) => v[0] === newValue)
if (idx > -1) {
this.$message.warning('当前值已存在!')
} else {
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }])
}
}
},
deleteValue(item) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList.splice(idx, 1)
this.valueList = _valueList
}
},
editValue(item, newValue, newStyle, newIcon) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }]
this.valueList = _valueList
}
},
getData() {
if (this.activeKey === 'define') {
return {
choice_value: this.valueList,
choice_web_hook: null,
choice_other: null,
}
} else if (this.activeKey === 'webhook') {
const choice_web_hook = this.$refs.webhook.getParams()
choice_web_hook.ret_key = this.form.ret_key
return { choice_value: [], choice_web_hook, choice_other: null }
} else {
let choice_other = {}
if (this.choice_other.type_ids && this.choice_other.type_ids.length) {
this.$refs.filterComp.handleSubmit()
choice_other = { ...this.choice_other, filter: this.filterExp }
}
return {
choice_value: [],
choice_web_hook: null,
choice_other,
}
}
},
setData({ choice_value, choice_web_hook, choice_other }) {
if (choice_web_hook) {
this.activeKey = 'webhook'
this.$nextTick(() => {
this.$refs.webhook.setParams(choice_web_hook)
this.form.ret_key = choice_web_hook.ret_key ?? ''
})
} else if (choice_other) {
this.activeKey = 'choice_other'
const { type_ids, attr_id, filter } = choice_other
this.choice_other = { type_ids, attr_id }
this.filterExp = filter
if (type_ids && type_ids.length) {
this.$nextTick(() => {
this.$refs.filterComp.visibleChange(true, false)
})
}
} else {
this.valueList = choice_value
this.activeKey = 'define'
}
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (this.disabled) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
setExpFromFilter(filterExp) {
if (filterExp) {
this.filterExp = `${filterExp}`
} else {
this.filterExp = ''
}
},
},
}
</script>
<style lang="less" scoped>
.pre-value-edit-color {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
.pre-value-edit-color-item {
cursor: pointer;
display: inline-block;
width: 25px;
height: 20px;
margin: 5px;
}
}
</style>
<style lang="less">
.pre-value-filter {
.ant-form-item-control {
line-height: 24px;
}
.table-filter-add {
line-height: 40px;
}
}
</style>

View File

@@ -156,12 +156,74 @@
</a-form-model-item>
<a-form-model-item label="通知方式" prop="method">
<a-checkbox-group v-model="notifies.method">
<a-checkbox value="wechatApp">
微信
</a-checkbox>
<a-checkbox value="email">
邮件
</a-checkbox>
<a-row :style="{ marginTop: '4px' }" :gutter="[0, 12]">
<a-col :span="6">
<a-checkbox value="email"> <ops-icon type="email" style="margin-right:5px" />邮件 </a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="wechatApp">
<ops-icon type="wechatApp" style="margin-right:5px" />企业微信
</a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="dingdingApp">
<ops-icon type="dingdingApp" style="margin-right:5px" />钉钉
</a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="feishuApp"> <ops-icon type="feishuApp" style="margin-right:5px" />飞书 </a-checkbox>
</a-col>
<a-col :span="4" :style="{ lineHeight: '32px' }">
<ops-icon type="robot" style="margin-right:5px" />机器人
</a-col>
<a-col :span="18">
<treeselect
:disable-branch-nodes="true"
:class="{
'custom-treeselect': true,
'custom-treeselect-bgcAndBorder': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '14px',
}"
v-model="selectedBot"
:multiple="true"
:clearable="true"
searchable
:options="appBot"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择机器人"
:normalizer="
(node) => {
return {
id: node.name,
label: node.label || node.name,
children: node.bot,
}
}
"
appendToBody
:zIndex="1050"
noChildrenText="暂无数据"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
<ops-icon :type="node.id" v-if="node.children" />{{ node.label }}
</div>
<div slot="value-label" slot-scope="{ node }">
<ops-icon :type="node.parentNode.id" />{{ node.label }}
</div>
</treeselect>
</a-col>
</a-row>
</a-checkbox-group>
</a-form-model-item>
</a-form-model>
@@ -200,6 +262,7 @@ import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vu
import Webhook from '../../components/webhook'
import NoticeContent from '../../components/noticeContent'
import { getNoticeByEmployeeIds } from '@/api/employee'
import { getNoticeConfigAppBot } from '@/api/noticeSetting'
export default {
name: 'TriggerForm',
@@ -260,6 +323,8 @@ export default {
isShow: false,
dag_id: null,
showCustomEmail: false,
appBot: [],
selectedBot: undefined,
}
},
computed: {
@@ -286,9 +351,15 @@ export default {
this.dags = res.map((dag) => ({ id: dag[1], label: dag[0] }))
})
},
async getNoticeConfigAppBot() {
await getNoticeConfigAppBot().then((res) => {
this.appBot = res
})
},
createFromTriggerTable(attrList) {
this.visible = true
// this.getDags()
this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList
this.triggerId = null
this.title = '新增触发器'
@@ -307,7 +378,8 @@ export default {
},
async open(property, attrList) {
this.visible = true
// await this.getDags()
await this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList
if (property.has_trigger) {
this.triggerId = property.trigger.id
@@ -348,7 +420,7 @@ export default {
const employee_ids = property?.trigger?.option?.employee_ids ?? undefined
const custom_email =
tos
.filter((t) => !t.employee_id)
.filter((t) => !t.employee_id && t.email)
.map((t) => t.email)
.join(';') ?? ''
@@ -360,7 +432,16 @@ export default {
this.$refs.noticeContent.setContent(body_html)
}, 100)
}
this.notifies = { employee_ids, custom_email, subject, method }
const _method = method.filter((item) => ['email', 'wechatApp', 'dingdingApp', 'feishuApp'].includes(item))
const _flatAppBot = []
this.appBot.forEach((item) => {
_flatAppBot.push(...item.bot.map((b) => b.name))
})
const selectedBot = method.filter(
(item) => !['email', 'wechatApp', 'dingdingApp', 'feishuApp'].includes(item) && _flatAppBot.includes(item)
)
this.selectedBot = selectedBot
this.notifies = { employee_ids, custom_email, subject, method: _method }
}
} else {
this.title = `新增触发器 ${property.alias || property.name}`
@@ -378,6 +459,7 @@ export default {
this.category = 1
this.triggerAction = '1'
this.filterExp = ''
this.selectedBot = undefined
this.visible = false
},
filterChange(value) {
@@ -415,11 +497,30 @@ export default {
tos.push({ email })
})
}
if (this.selectedBot && this.selectedBot.length) {
this.selectedBot.forEach((bot) => {
tos.push({ [`${bot}`]: bot })
})
}
if (this.category === 2) {
const { before_days, notify_at } = this.dateForm
params.option.notifies = { tos, subject, body, body_html, method, before_days, notify_at }
params.option.notifies = {
tos,
subject,
body,
body_html,
method: [...method, ...(this.selectedBot ?? [])],
before_days,
notify_at,
}
} else {
params.option.notifies = { tos, subject, body, body_html, method }
params.option.notifies = {
tos,
subject,
body,
body_html,
method: [...method, ...(this.selectedBot ?? [])],
}
}
break
case '2':

View File

@@ -29,6 +29,12 @@
<span v-else-if="row.notify">通知</span>
</template>
</vxe-column>
<vxe-column title="状态">
<template #default="{ row }">
<a-tag color="green" v-if="row.is_ok">已完成</a-tag>
<a-tag color="red" v-else>未完成</a-tag>
</template>
</vxe-column>
<vxe-column title="触发时间">
<template #default="{row}">
{{ row.updated_at || row.created_at }}