relation view define [done]

This commit is contained in:
pycook 2019-11-27 18:25:53 +08:00
parent e977bb15a5
commit 73b92ff533
36 changed files with 535 additions and 43 deletions

2
.gitignore vendored
View File

@ -73,4 +73,4 @@ ui/npm-debug.log*
ui/yarn-debug.log*
ui/yarn-error.log*
ui/yarn.lock
ui/package-lock.json

View File

@ -80,9 +80,9 @@ class PreferenceRelationApiView(APIView):
def post(self):
name = request.values.get("name")
cr_ids = request.values.get("cr_ids")
res = PreferenceManager.create_or_update_relation_view(name, cr_ids)
views, id2type = PreferenceManager.create_or_update_relation_view(name, cr_ids)
return self.jsonify(res)
return self.jsonify(views, id2type)
def put(self):
return self.post()

View File

@ -1,3 +1,3 @@
NODE_ENV=production
VUE_APP_PREVIEW=false
VUE_APP_API_BASE_URL=http://127.0.0.1:5000/api
VUE_APP_API_BASE_URL=http://127.0.0.1:5001/api

View File

@ -1,3 +1,3 @@
NODE_ENV=production
VUE_APP_PREVIEW=true
VUE_APP_API_BASE_URL=http://127.0.0.1:5000/api
VUE_APP_API_BASE_URL=http://127.0.0.1:5001/api

View File

@ -2,13 +2,13 @@ module.exports = {
presets: [
'@vue/app',
[
"@babel/preset-env",
'@babel/preset-env',
{
"useBuiltIns": "usage", // "usage" | "entry" | false, defaults to false.
"corejs": "3.1.2",
"targets": {
"esmodules": true,
"ie": "11"
'useBuiltIns': 'usage', // "usage" | "entry" | false, defaults to false.
'corejs': '3.1.2',
'targets': {
'esmodules': true,
'ie': '11'
}
}
]

View File

@ -28,6 +28,7 @@
"md5": "^2.2.1",
"moment": "^2.24.0",
"nprogress": "^0.2.0",
"vis-network": "^6.4.4",
"viser-vue": "^2.3.3",
"vue": "^2.6.10",
"vue-clipboard2": "^0.2.1",

View File

@ -3,7 +3,7 @@ import { axios } from '@/utils/request'
export function getCITypeChildren (CITypeID, parameter) {
return axios({
url: '/v0.1/ci_type_relations/' + CITypeID + '/children',
method: 'get',
method: 'GET',
params: parameter
})
}
@ -11,14 +11,21 @@ export function getCITypeChildren (CITypeID, parameter) {
export function getCITypeParent (CITypeID) {
return axios({
url: '/v0.1/ci_type_relations/' + CITypeID + '/parents',
method: 'get'
method: 'GET'
})
}
export function getCITypeRelations () {
return axios({
url: '/v0.1/ci_type_relations',
method: 'GET'
})
}
export function createRelation (parentId, childrenId, relationTypeId) {
return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'post',
method: 'POST',
data: { relation_type_id: relationTypeId }
})
}
@ -26,7 +33,7 @@ export function createRelation (parentId, childrenId, relationTypeId) {
export function deleteRelation (parentId, childrenId) {
return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'delete'
method: 'DELETE'
})
}

View File

@ -39,3 +39,26 @@ export function subscribeTreeView (ciTypeId, levels) {
data: { type_id: ciTypeId, levels: levels }
})
}
export function getRelationView () {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'GET'
})
}
export function deleteRelationView (viewName) {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'DELETE',
data: { name: viewName }
})
}
export function subscribeRelationView (payload) {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'POST',
data: payload
})
}

View File

@ -62,14 +62,14 @@ const cmdbRouter = [
path: '/config/ci_types',
name: 'ci_type',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/model_config/ci_type/list'),
component: () => import('@/views/cmdb/modeling/ci_type/list'),
meta: { title: '模型管理', keepAlive: true }
},
{
path: '/config/ci_types/:CITypeName/detail/:CITypeId',
name: 'ci_type_detail',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/model_config/ci_type/detail'),
component: () => import('@/views/cmdb/modeling/ci_type/detail'),
meta: { title: '模型管理', keepAlive: true, hidden: true },
hidden: true
},
@ -77,22 +77,22 @@ const cmdbRouter = [
path: '/config/attributes',
name: 'attributes',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/model_config/attributes/index'),
component: () => import('@/views/cmdb/modeling/attributes/index'),
meta: { title: '属性库', keepAlive: true }
},
{
path: '/config/relation_type',
name: 'relation_type',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/model_config/relation_type/index'),
component: () => import('@/views/cmdb/modeling/relation_type/index'),
meta: { title: '关系类型', keepAlive: true }
},
{
path: '/config/preference_relation',
name: 'preference_relation',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/model_config/preference_relation/index'),
meta: { title: '关系视图配置', keepAlive: true }
component: () => import('@/views/cmdb/modeling/preference_relation/index'),
meta: { title: '关系视图定义', keepAlive: true }
}
]
},
@ -107,28 +107,28 @@ const cmdbRouter = [
path: '/acl/users',
name: 'acl_users',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/acl/users'),
component: () => import('@/views/acl/users'),
meta: { title: '用户管理', keepAlive: true }
},
{
path: '/acl/roles',
name: 'acl_roles',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/acl/roles'),
component: () => import('@/views/acl/roles'),
meta: { title: '角色管理', keepAlive: true }
},
{
path: '/acl/resources',
name: 'acl_resources',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/acl/resources'),
component: () => import('@/views/acl/resources'),
meta: { title: '资源管理', keepAlive: true }
},
{
path: '/acl/resource_types',
name: 'acl_resource_types',
hideChildrenInMenu: true,
component: () => import('@/views/cmdb/acl/resource_types'),
component: () => import('@/views/acl/resource_types'),
meta: { title: '资源类型', keepAlive: true }
}
]

View File

@ -1,6 +0,0 @@
<template>
<div></div>
</template>
<script>
export default {}
</script>

View File

@ -143,8 +143,8 @@ import {
} from '@/api/cmdb/CITypeAttr'
import { STable } from '@/components'
import { mixin, mixinDevice } from '@/utils/mixin'
import AttributeForm from '@/views/cmdb/model_config/attributes/module/attributeForm'
import { valueTypeMap } from '@/views/cmdb/model_config/attributes/module/const'
import AttributeForm from '@/views/cmdb/modeling/attributes/module/attributeForm'
import { valueTypeMap } from '@/views/cmdb/modeling/attributes/module/const'
export default {
name: 'AttributesTable',

View File

@ -0,0 +1,312 @@
<template>
<div>
<a-card :bordered="true" title="关系视图定义面板">
<a-card-meta description="点击右键可选择节点"></a-card-meta>
<div
id="visualization"
style="height:400px"
@mousedown="mouseDown"
@mouseup="mouseUp"
@mousemove="mouseMove">
</div>
<relation-view-form ref="relationViewForm"></relation-view-form>
</a-card>
<a-row :gutter="0">
<a-col
:xl="12"
:lg="12"
:md="12"
:sm="24"
:xs="24"
:key="view"
v-for="view in Object.keys(relationViews.views)">
<a-card :bordered="true" :title="view">
<a slot="extra"><a-icon type='close' @click="deleteView(view)"></a-icon></a>
<div :id="&quot;view-&quot; + (relationViews.views[view] || []).join(&quot;&quot;)" style="height:200px"></div>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script>
// 按需引入
import { DataSet, Network } from 'vis-network'
import { getCITypeRelations } from '@/api/cmdb/CITypeRelation'
import { getRelationView, deleteRelationView } from '@/api/cmdb/preference'
import RelationViewForm from './modules/RelationViewForm'
import { notification } from 'ant-design-vue'
export default {
components: {
RelationViewForm
},
data () {
return {
relationViews: { views: {} },
relations: [],
network: null,
container: null,
nodes: null,
edges: null,
canvas: null,
ctx: null,
drag: false,
rect: {},
drawingSurfaceImageData: null
}
},
created () {
this.create()
},
methods: {
create () {
getRelationView().then(res => {
this.relationViews = res
this.createRelationViews()
})
getCITypeRelations().then(res => {
// create an array with nodes
this.relations = res
const nodes = []
const edges = []
const nodeMap = {}
res.forEach(item => {
if (!(item.child.id in nodeMap)) {
nodes.push({
id: item.child.id,
label: item.child.alias
})
}
if (!(item.parent.id in nodeMap)) {
nodes.push({
id: item.parent.id,
label: item.parent.alias
})
}
nodeMap[item.child.id] = 1
nodeMap[item.parent.id] = 1
edges.push({
from: item.parent.id,
to: item.child.id,
label: item.relation_type.name
})
})
const _nodes = new DataSet(nodes)
const _edges = new DataSet(edges)
this.nodes = _nodes
this.edges = _edges
// create a network
this.container = document.querySelector('#visualization')
// provide the data in the vis format
var data = {
nodes: _nodes,
edges: _edges
}
var options = {
layout: { randomSeed: 2 },
autoResize: true,
nodes: {
size: 30,
font: {
size: 14
},
borderWidth: 2
},
edges: {
width: 2,
smooth: {
enabled: false
},
arrows: {
to: {
enabled: true,
scaleFactor: 0.5
}
}
},
physics: {
enabled: false
},
interaction: {
hover: true,
dragView: true,
dragNodes: true,
multiselect: true
}
}
// initialize your network!
this.container.oncontextmenu = () => { return false }
this.network = new Network(this.container, data, options)
this.canvas = this.network.canvas.frame.canvas
this.ctx = this.canvas.getContext('2d')
})
},
createRelationViews () {
Object.keys(this.relationViews.views).forEach(viewName => {
const nodes = []
const edges = []
const len = this.relationViews.views[viewName].length
this.relationViews.views[viewName].slice(0, len - 1).forEach((fromId, idx) => {
nodes.push({
id: fromId,
label: this.relationViews.id2type[fromId].alias
})
edges.push({
from: fromId,
to: this.relationViews.views[viewName][idx + 1]
})
})
nodes.push({
id: this.relationViews.views[viewName][len - 1],
label: this.relationViews.id2type[this.relationViews.views[viewName][len - 1]].alias
})
const _nodes = new DataSet(nodes)
const _edges = new DataSet(edges)
var data = {
nodes: _nodes,
edges: _edges
}
var options = {
layout: { randomSeed: 2 },
nodes: {
size: 30,
font: {
size: 14
},
borderWidth: 2
},
edges: {
width: 2,
smooth: {
enabled: false
},
arrows: {
to: {
enabled: true,
scaleFactor: 0.5
}
}
},
physics: {
enabled: false
},
interaction: {
hover: true,
dragView: false,
dragNodes: false
}
}
setTimeout(() => {
const container = document.querySelector('#view-' + this.relationViews.views[viewName].join(''))
const n = new Network(container, data, options)
console.log(n)
}, 100)
})
},
toggleCreate (nodeIds) {
const crIds = []
this.relations.forEach(item => {
if (nodeIds.includes(item.parent_id) && nodeIds.includes(item.child_id)) {
crIds.push({
parent_id: item.parent_id,
child_id: item.child_id
})
}
})
this.$refs.relationViewForm.handleCreate(crIds)
},
saveDrawingSurface () {
this.drawingSurfaceImageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
},
restoreDrawingSurface () {
this.ctx.putImageData(this.drawingSurfaceImageData, 0, 0)
},
selectNodesFromHighlight () {
var nodesIdInDrawing = []
var xRange = this.getStartToEnd(this.rect.startX, this.rect.w)
var yRange = this.getStartToEnd(this.rect.startY, this.rect.h)
var allNodes = this.nodes.get()
for (var i = 0; i < allNodes.length; i++) {
var curNode = allNodes[i]
var nodePosition = this.network.getPositions([curNode.id])
var nodeXY = this.network.canvasToDOM({ x: nodePosition[curNode.id].x, y: nodePosition[curNode.id].y })
if (xRange.start <= nodeXY.x && nodeXY.x <= xRange.end && yRange.start <= nodeXY.y && nodeXY.y <= yRange.end) {
nodesIdInDrawing.push(curNode.id)
}
}
this.toggleCreate(nodesIdInDrawing)
this.network.selectNodes(nodesIdInDrawing)
},
getStartToEnd (start, theLen) {
return theLen > 0 ? { start: start, end: start + theLen } : { start: start + theLen, end: start }
},
mouseMove () {
if (this.drag) {
this.restoreDrawingSurface()
this.rect.w = event.offsetX - this.rect.startX
this.rect.h = event.offsetY - this.rect.startY
this.ctx.setLineDash([5])
this.ctx.strokeStyle = 'rgb(0, 102, 0)'
this.ctx.strokeRect(this.rect.startX, this.rect.startY, this.rect.w, this.rect.h)
this.ctx.setLineDash([])
this.ctx.fillStyle = 'rgba(0, 255, 0, 0.2)'
this.ctx.fillRect(this.rect.startX, this.rect.startY, this.rect.w, this.rect.h)
}
},
mouseDown () {
if (event.button === 2) {
this.saveDrawingSurface()
this.rect.startX = event.offsetX
this.rect.startY = event.offsetY
this.drag = true
this.container.style.cursor = 'crosshair'
}
},
mouseUp () {
if (event.button === 2) {
this.restoreDrawingSurface()
this.drag = false
this.container.style.cursor = 'default'
this.selectNodesFromHighlight()
}
},
deleteView (viewName) {
const that = this
this.$confirm({
title: '警告',
content: '确认要删除吗 ?',
onOk () {
deleteRelationView(viewName).then(res => {
that.create()
}).catch(e => {
notification.error({ message: e.reponse.data.message })
})
}
})
}
}
}
</script>

View File

@ -0,0 +1,159 @@
<template>
<a-drawer
:closable="false"
:title="drawerTitle"
:visible="drawerVisible"
@close="onClose"
placement="right"
width="30%"
>
<a-form :form="form" :layout="formLayout" @submit="handleSubmit">
<a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
label="关系视图名"
>
<a-input
name="name"
placeholder=""
v-decorator="['name', {rules: [{ required: true, message: '请输入 关系视图名'}]} ]"
/>
</a-form-item>
<a-form-item>
<a-input
name="id"
type="hidden"
v-decorator="['id', {rules: []} ]"
/>
</a-form-item>
<div
:style="{
position: 'absolute',
left: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '0.8rem 1rem',
background: '#fff',
}"
>
<a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
<a-button @click="onClose">取消</a-button>
</div>
</a-form>
</a-drawer>
</template>
<script>
import { subscribeRelationView } from '@/api/cmdb/preference'
export default {
name: 'RelationViewForm',
data () {
return {
drawerTitle: '新增关系视图',
drawerVisible: false,
formLayout: 'vertical',
crIds: []
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
computed: {
formItemLayout () {
const { formLayout } = this
return formLayout === 'horizontal' ? {
labelCol: { span: 4 },
wrapperCol: { span: 14 }
} : {}
},
horizontalFormItemLayout () {
return {
labelCol: { span: 5 },
wrapperCol: { span: 12 }
}
},
buttonItemLayout () {
const { formLayout } = this
return formLayout === 'horizontal' ? {
wrapperCol: { span: 14, offset: 4 }
} : {}
}
},
methods: {
handleCreate (crIds) {
this.crIds = crIds
this.drawerVisible = true
},
onClose () {
this.form.resetFields()
this.drawerVisible = false
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values)
this.createRelationView(values)
}
})
},
createRelationView (data) {
data.cr_ids = this.crIds
subscribeRelationView(data)
.then(res => {
this.$message.success(`添加成功`)
this.onClose()
})
.catch(err => this.requestFailed(err))
},
requestFailed (err) {
console.log(err, 'error')
const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
this.$message.error(`${msg}`)
}
}
}
</script>
<style lang="less" scoped>
.search {
margin-bottom: 54px;
}
.fold {
width: calc(100% - 216px);
display: inline-block
}
.operator {
margin-bottom: 18px;
}
.action-btn {
margin-bottom: 1rem;
}
@media screen and (max-width: 900px) {
.fold {
width: 100%;
}
}
</style>

View File

@ -80,7 +80,7 @@
<script>
import { STable } from '@/components'
import RelationTypeForm from './module/relationTypeForm'
import RelationTypeForm from './modules/relationTypeForm'
import { getRelationTypes, deleteRelationType } from '@/api/cmdb/relationType'
export default {

View File

@ -18,7 +18,7 @@
<a-input
name="name"
placeholder=""
v-decorator="['name', {rules: [{ required: true, message: '请输入资源'}]} ]"
v-decorator="['name', {rules: [{ required: true, message: '请输入类型'}]} ]"
/>
</a-form-item>
@ -53,14 +53,10 @@
</template>
<script>
import { STable } from '@/components'
import { addRelationType, updateRelationType } from '@/api/cmdb/relationType'
export default {
name: 'RelationTypeForm',
components: {
STable
},
data () {
return {
drawerTitle: '新增关系类型',
@ -128,14 +124,14 @@ export default {
console.log('Received values of form: ', values)
if (values.id) {
this.updateResourceType(values.id, values)
this.updateRelationType(values.id, values)
} else {
this.createResourceType(values)
this.createRelationType(values)
}
}
})
},
updateResourceType (id, data) {
updateRelationType (id, data) {
updateRelationType(id, data)
.then(res => {
this.$message.success(`更新成功`)
@ -144,7 +140,7 @@ export default {
}).catch(err => this.requestFailed(err))
},
createResourceType (data) {
createRelationType (data) {
addRelationType(data)
.then(res => {
this.$message.success(`添加成功`)