ACL: permission management [doing]

This commit is contained in:
pycook 2019-12-04 18:14:09 +08:00
parent f70ed54cad
commit 16b724bd40
19 changed files with 396 additions and 291 deletions

View File

@ -3,52 +3,47 @@
import functools import functools
import six import six
from flask import current_app, g, request from flask import current_app, g, request
from flask import session, abort from flask import session, abort
from api.extensions import cache from api.lib.perm.acl.cache import AppCache
from api.models.acl import ResourceType
from api.models.acl import Resource
def get_access_token(): from api.lib.perm.acl.resource import ResourceCRUD
return
class AccessTokenCache(object):
@classmethod
def get(cls):
if cache.get("AccessToken") is not None:
return cache.get("AccessToken")
res = get_access_token() or ""
cache.set("AccessToken", res, timeout=60 * 60)
return res
@classmethod
def clean(cls):
cache.clear("AccessToken")
class ACLManager(object): class ACLManager(object):
def __init__(self): def __init__(self):
self.access_token = AccessTokenCache.get()
self.acl_session = dict(uid=session.get("uid"),
token=self.access_token)
self.user_info = session["acl"] if "acl" in session else {} self.user_info = session["acl"] if "acl" in session else {}
self.app_id = AppCache.get('cmdb')
if not self.app_id:
raise Exception("cmdb not in acl apps")
self.app_id = self.app_id.id
def add_resource(self, name, resource_type_name=None): def add_resource(self, name, resource_type_name=None):
pass resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
ResourceCRUD.add(name, resource_type.id, self.app_id)
def grant_resource_to_role(self, name, role, resource_type_name=None): def grant_resource_to_role(self, name, role, resource_type_name=None):
pass resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
def del_resource(self, name, resource_type_name=None): def del_resource(self, name, resource_type_name=None):
pass resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
def get_user_info(self, username): resource = Resource.get_by(resource_type_id=resource_type.id,
return dict() app_id=self.app_id,
name=name,
first=True,
to_dict=False)
if resource:
ResourceCRUD.delete(resource.id)
def get_resources(self, resource_type_name=None): def get_resources(self, resource_type_name=None):
if "acl" not in session: if "acl" not in session:
@ -87,7 +82,9 @@ def can_access_resources(resource_type):
else: else:
g.resources = {resource_type: result} g.resources = {resource_type: result}
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper_can_access_resources return wrapper_can_access_resources
return decorator_can_access_resources return decorator_can_access_resources
@ -102,7 +99,9 @@ def has_perm(resources, resource_type, perm):
validate_permission(resources, resource_type, perm) validate_permission(resources, resource_type, perm)
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper_has_perm return wrapper_has_perm
return decorator_has_perm return decorator_has_perm
@ -120,7 +119,9 @@ def has_perm_from_args(arg_name, resource_type, perm, callback=None):
validate_permission(resource, resource_type, perm) validate_permission(resource, resource_type, perm)
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper_has_perm return wrapper_has_perm
return decorator_has_perm return decorator_has_perm
@ -135,5 +136,7 @@ def role_required(role_name):
if role_name not in session.get("acl", {}).get("parentRoles", []): if role_name not in session.get("acl", {}).get("parentRoles", []):
return abort(403, "Role {0} is required".format(role_name)) return abort(403, "Role {0} is required".format(role_name))
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper_role_required return wrapper_role_required
return decorator_role_required return decorator_role_required

View File

@ -36,11 +36,11 @@ class ResourceTypeCRUD(object):
return [i.to_dict() for i in perms] return [i.to_dict() for i in perms]
@classmethod @classmethod
def add(cls, app_id, name, perms): def add(cls, app_id, name, description, perms):
ResourceType.get_by(name=name, app_id=app_id) and abort( ResourceType.get_by(name=name, app_id=app_id) and abort(
400, "ResourceType <{0}> is already existed".format(name)) 400, "ResourceType <{0}> is already existed".format(name))
rt = ResourceType.create(name=name, app_id=app_id) rt = ResourceType.create(name=name, description=description, app_id=app_id)
cls.update_perms(rt.id, perms, app_id) cls.update_perms(rt.id, perms, app_id)

View File

@ -23,8 +23,8 @@ class RoleRelationCRUD(object):
if uids is not None: if uids is not None:
uids = [uids] if isinstance(uids, six.integer_types) else uids uids = [uids] if isinstance(uids, six.integer_types) else uids
rids = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.in_(uids)) rids = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.in_(uids))
rid2uid = {i.rid: i.uid for i in rids} rid2uid = {i.id: i.uid for i in rids}
rids = [i.rid for i in rids] rids = [i.id for i in rids]
else: else:
rids = [rids] if isinstance(rids, six.integer_types) else rids rids = [rids] if isinstance(rids, six.integer_types) else rids
@ -98,9 +98,12 @@ class RoleRelationCRUD(object):
class RoleCRUD(object): class RoleCRUD(object):
@staticmethod @staticmethod
def search(q, app_id, page=1, page_size=None): def search(q, app_id, page=1, page_size=None, user_role=False):
query = db.session.query(Role).filter(Role.deleted.is_(False)).filter( query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.app_id == app_id)
Role.app_id == app_id).filter(Role.uid.is_(None))
if not user_role:
query = query.filter(Role.uid.is_(None))
if q: if q:
query = query.filter(Role.name.ilike('%{0}%'.format(q))) query = query.filter(Role.name.ilike('%{0}%'.format(q)))
@ -109,7 +112,7 @@ class RoleCRUD(object):
return numfound, query.offset((page - 1) * page_size).limit(page_size) return numfound, query.offset((page - 1) * page_size).limit(page_size)
@staticmethod @staticmethod
def add_role(name, app_id, is_app_admin=False, uid=None): def add_role(name, app_id=None, is_app_admin=False, uid=None):
Role.get_by(name=name, app_id=app_id) and abort(400, "Role <{0}> is already existed".format(name)) Role.get_by(name=name, app_id=app_id) and abort(400, "Role <{0}> is already existed".format(name))
return Role.create(name=name, return Role.create(name=name,

View File

@ -1,15 +1,17 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import uuid
import string
import random import random
import string
import uuid
from flask import abort from flask import abort
from flask import g from flask import g
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.role import RoleCRUD
from api.models.acl import Role
from api.models.acl import User from api.models.acl import User
@ -40,14 +42,28 @@ class UserCRUD(object):
kwargs['block'] = 0 kwargs['block'] = 0
kwargs['key'], kwargs['secret'] = cls._gen_key_secret() kwargs['key'], kwargs['secret'] = cls._gen_key_secret()
return User.create(**kwargs) user = User.create(**kwargs)
RoleCRUD.add_role(user.username, uid=user.uid)
return user
@staticmethod @staticmethod
def update(uid, **kwargs): def update(uid, **kwargs):
user = User.get_by(uid=uid, to_dict=False, first=True) or abort(404, "User <{0}> does not exist".format(uid)) user = User.get_by(uid=uid, to_dict=False, first=True) or abort(404, "User <{0}> does not exist".format(uid))
if kwargs.get("username"):
other = User.get_by(username=kwargs['username'], first=True, to_dict=False)
if other is not None and other.uid != user.uid:
return abort(400, "User <{0}> cannot be duplicated".format(kwargs['username']))
UserCache.clean(user) UserCache.clean(user)
if kwargs.get("username") and kwargs['username'] != user.username:
role = Role.get_by(name=user.username, first=True, to_dict=False)
if role is not None:
RoleCRUD.update_role(role.id, **dict(name=kwargs['name']))
return user.update(**kwargs) return user.update(**kwargs)
@classmethod @classmethod

View File

@ -39,9 +39,10 @@ class ResourceTypeView(APIView):
def post(self): def post(self):
name = request.values.get('name') name = request.values.get('name')
app_id = request.values.get('app_id') app_id = request.values.get('app_id')
description = request.values.get('description', '')
perms = request.values.get('perms') perms = request.values.get('perms')
rt = ResourceTypeCRUD.add(app_id, name, perms) rt = ResourceTypeCRUD.add(app_id, name, description, perms)
return self.jsonify(rt.to_dict()) return self.jsonify(rt.to_dict())

View File

@ -21,8 +21,9 @@ class RoleView(APIView):
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
q = request.values.get('q') q = request.values.get('q')
app_id = request.values.get('app_id') app_id = request.values.get('app_id')
user_role = request.values.get('user_role', False)
numfound, roles = RoleCRUD.search(q, app_id, page, page_size) numfound, roles = RoleCRUD.search(q, app_id, page, page_size, user_role)
id2parents = RoleRelationCRUD.get_parents([i.id for i in roles]) id2parents = RoleRelationCRUD.get_parents([i.id for i in roles])

View File

@ -105,28 +105,28 @@ const cmdbRouter = [
children: [ children: [
{ {
path: '/acl/users', path: '/acl/users',
name: 'acl_users', name: 'cmdb_acl_users',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('@/views/acl/users'), component: () => import('@/views/acl/users'),
meta: { title: '用户管理', keepAlive: true } meta: { title: '用户管理', keepAlive: true }
}, },
{ {
path: '/acl/roles', path: '/acl/roles',
name: 'acl_roles', name: 'cmdb_acl_roles',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('@/views/acl/roles'), component: () => import('@/views/acl/roles'),
meta: { title: '角色管理', keepAlive: true } meta: { title: '角色管理', keepAlive: true }
}, },
{ {
path: '/acl/resources', path: '/acl/resources',
name: 'acl_resources', name: 'cmdb_acl_resources',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('@/views/acl/resources'), component: () => import('@/views/acl/resources'),
meta: { title: '资源管理', keepAlive: true } meta: { title: '资源管理', keepAlive: true }
}, },
{ {
path: '/acl/resource_types', path: '/acl/resource_types',
name: 'acl_resource_types', name: 'cmdb_acl_resource_types',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('@/views/acl/resource_types'), component: () => import('@/views/acl/resource_types'),
meta: { title: '资源类型', keepAlive: true } meta: { title: '资源类型', keepAlive: true }

View File

@ -167,7 +167,7 @@ export default {
if (!err) { if (!err) {
console.log('Received values of form: ', values) console.log('Received values of form: ', values)
values.app_id = this.$store.state.app.name values.app_id = this.$route.name.split('_')[0]
values.perms = this.perms values.perms = this.perms
if (values.id) { if (values.id) {
this.updateResourceType(values.id, values) this.updateResourceType(values.id, values)

View File

@ -64,7 +64,7 @@
<script> <script>
import { STable } from '@/components' import { STable } from '@/components'
import { addResource, updateResourceById, searchResourceType } from '@/api/acl/resource' import { addResource, searchResourceType } from '@/api/acl/resource'
export default { export default {
name: 'ResourceForm', name: 'ResourceForm',
@ -116,40 +116,31 @@ export default {
}, },
methods: { methods: {
getAllResourceTypes () { getAllResourceTypes () {
searchResourceType({ page_size: 9999, app_id: this.$store.state.app.name }).then(res => { searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then(res => {
this.allTypes = res.groups this.allTypes = res.groups
}) })
}, },
handleCreate () { handleCreate (defaultType) {
this.drawerVisible = true this.drawerVisible = true
this.$nextTick(() => {
this.form.setFieldsValue({ type_id: defaultType.id })
})
}, },
onClose () { onClose () {
this.form.resetFields() this.form.resetFields()
this.drawerVisible = false this.drawerVisible = false
this.$emit('fresh')
}, },
onChange (e) { onChange (e) {
console.log(`checked = ${e}`) console.log(`checked = ${e}`)
}, },
handleEdit (record) {
this.drawerVisible = true
console.log(record)
this.$nextTick(() => {
this.form.setFieldsValue({
id: record.id,
name: record.name,
type_id: record.resource_type_id
})
})
},
handleSubmit (e) { handleSubmit (e) {
e.preventDefault() e.preventDefault()
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
console.log('Received values of form: ', values) console.log('Received values of form: ', values)
values.app_id = this.$store.state.app.name values.app_id = this.$route.name.split('_')[0]
if (values.id) { if (values.id) {
this.updateResource(values.id, values) this.updateResource(values.id, values)
} else { } else {
@ -158,20 +149,10 @@ export default {
} }
}) })
}, },
updateResource (id, data) {
updateResourceById(id, data)
.then(res => {
this.$message.success(`更新成功`)
this.handleOk()
this.onClose()
}).catch(err => this.requestFailed(err))
},
createResource (data) { createResource (data) {
addResource(data) addResource(data)
.then(res => { .then(res => {
this.$message.success(`添加成功`) this.$message.success(`添加成功`)
this.handleOk()
this.onClose() this.onClose()
}) })
.catch(err => this.requestFailed(err)) .catch(err => this.requestFailed(err))
@ -182,13 +163,6 @@ export default {
this.$message.error(`${msg}`) this.$message.error(`${msg}`)
} }
},
watch: {},
props: {
handleOk: {
type: Function,
default: null
}
} }
} }

View File

@ -1,80 +1,37 @@
<template> <template>
<a-modal <a-modal
:title="drawerTitle" :title="drawerTitle"
v-model="drawerVisible" v-model="drawerVisible"
width="50%" width="50%"
> >
<template slot="footer"> <template slot="footer">
<a-button type="primary" @click="showChildrenDrawer">
添加权限
</a-button>
<a-button key="back" @click="handleCancel">关闭</a-button> <a-button key="back" @click="handleCancel">关闭</a-button>
</template> </template>
<template> <template>
<a-list itemLayout="horizontal">
<a-list itemLayout="horizontal" :dataSource="resPerms"> <a-list-item v-for="item in resPerms" :key="item[0]">
<a-list-item slot="renderItem" slot-scope="item">
<span>{{ item[0] }} </span> <span>{{ item[0] }} </span>
<div> <div>
<a-tag color="cyan" v-for="perm in item[1]" :key="perm.rid" closable @close="deletePerm(perm.rid, perm.name)">{{ perm.name }}</a-tag> <a-tag
closable
color="cyan"
v-for="perm in item[1]"
:key="perm.name"
@close="deletePerm(perm.rid, perm.name)">
{{ perm.name }}
</a-tag>
</div> </div>
</a-list-item> </a-list-item>
</a-list> </a-list>
</template> </template>
<a-drawer
title="添加权限"
width="30%"
:closable="false"
@close="onChildrenDrawerClose"
:visible="childrenDrawer"
>
<a-form :form="form" @submit="handleSubmit">
<a-form-item
label="角色列表"
>
<a-select name="roleID" v-decorator="['roleID', {rules: []} ]">
<a-select-option v-for="role in allRoles" :key="role.id">{{ role.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="权限列表"
>
<a-select name="permName" v-decorator="['permName', {rules: []} ]">
<a-select-option v-for="perm in allPerms" :key="perm.name">{{ perm.name }}</a-select-option>
</a-select>
</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="onChildrenDrawerClose">取消</a-button>
</div>
</a-form>
</a-drawer>
</a-modal> </a-modal>
</template> </template>
<script> <script>
import { STable } from '@/components' import { STable } from '@/components'
import { getResourceTypePerms, getResourcePerms, deleteRoleResourcePerm, setRoleResourcePerm } from '@/api/acl/permission' import { getResourceTypePerms, getResourcePerms, deleteRoleResourcePerm } from '@/api/acl/permission'
import { searchRole } from '@/api/acl/role'
export default { export default {
name: 'ResourceForm', name: 'ResourceForm',
@ -97,25 +54,13 @@ export default {
beforeCreate () { beforeCreate () {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
}, },
computed: {
},
mounted () {
},
methods: { methods: {
showChildrenDrawer () {
this.childrenDrawer = true
},
onChildrenDrawerClose () {
this.childrenDrawer = false
},
handlePerm (record) { handlePerm (record) {
this.drawerVisible = true this.drawerVisible = true
this.record = record this.record = record
this.getResPerms(record.id) this.getResPerms(record.id)
this.$nextTick(() => { this.$nextTick(() => {
this.getAllRoles()
this.getAllPerms(record.resource_type_id) this.getAllPerms(record.resource_type_id)
}) })
}, },
@ -136,52 +81,19 @@ export default {
deletePerm (roleID, permName) { deletePerm (roleID, permName) {
deleteRoleResourcePerm(roleID, this.record.id, { perms: [permName] }).then(res => { deleteRoleResourcePerm(roleID, this.record.id, { perms: [permName] }).then(res => {
this.$message.success(`删除成功`) this.$message.success(`删除成功`)
this.handleOk() }).catch(err => this.requestFailed(err))
})
.catch(err => this.requestFailed(err))
},
addPerm (roleID, permName) {
setRoleResourcePerm(roleID, this.record.id, { perms: [permName] }).then(res => {
this.$message.success(`添加成功`)
this.handleOk()
})
.catch(err => this.requestFailed(err))
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values)
this.addPerm(values.roleID, values.permName)
}
})
},
getAllRoles () {
searchRole({ page_size: 9999, app_id: this.$store.state.app.name }).then(res => {
this.allRoles = res.roles
})
},
handleCreate () {
this.drawerVisible = true
}, },
handleCancel (e) { handleCancel (e) {
this.drawerVisible = false this.drawerVisible = false
}, },
requestFailed (err) { requestFailed (err) {
console.log(err)
const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试' const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
this.$message.error(`${msg}`) this.$message.error(`${msg}`)
} }
}, },
watch: {}, watch: {}
props: {
handleOk: {
type: Function,
default: null
}
}
} }
</script> </script>

View File

@ -0,0 +1,120 @@
<template>
<a-drawer
:title="'添加授权:'+instance.name"
width="30%"
:closable="true"
:visible="visible"
@close="closeForm"
>
<a-form :form="form">
<a-form-item
label="角色列表"
>
<a-select
showSearch
name="roleIdList"
v-decorator="['roleIdList', {rules: []} ]"
mode="multiple"
placeholder="请选择角色名称,可多选!"
:filterOption="filterOption">
<a-select-option v-for="role in allRoles" :key="role.id">{{ role.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="权限列表"
>
<a-select name="permName" v-decorator="['permName', {rules: []} ]" mode="multiple" placeholder="请选择权限,可多选!">
<a-select-option v-for="perm in allPerms" :key="perm.name">{{ perm.name }}</a-select-option>
</a-select>
</a-form-item>
<div class="btn-group">
<a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">添加</a-button>
<a-button @click="closeForm">取消</a-button>
</div>
</a-form>
</a-drawer>
</template>
<script>
import { searchRole } from '@/api/acl/role'
import { getResourceTypePerms, setRoleResourcePerm } from '@/api/acl/permission'
export default {
name: 'ResourcePermManageForm',
data () {
return {
allRoles: [],
allPerms: [],
visible: false,
instance: {} // 当前对象
}
},
props: {
groupTypeMessage: {
required: true,
type: Object
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
mounted () {
this.loadRoles()
},
methods: {
loadRoles () {
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: 1 }).then(res => {
this.allRoles = res.roles
}).catch(err => this.requestFailed(err))
},
loadPerm (resourceTypeId) {
getResourceTypePerms(resourceTypeId).then(res => {
this.allPerms = res
}).catch(err => this.requestFailed(err))
},
closeForm () {
this.visible = false
this.form.resetFields()
},
editPerm (record) {
this.visible = true
this.instance = record
this.loadPerm(record['resource_type_id'])
},
requestFailed (err) {
const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
this.$message.error(`${msg}`)
},
filterOption (input, option) {
return (
option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
)
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
values.roleIdList.forEach(roleId => {
setRoleResourcePerm(roleId, this.instance.id, { perms: values.permName }).then(
res => { this.$message.info('添加授权成功') }).catch(
err => this.requestFailed(err))
})
}
})
}
}
}
</script>
<style lang="less" scoped>
.btn-group {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
border-top: 1px solid #e9e9e9;
padding: 0.8rem 1rem;
background: #fff;
}
</style>

View File

@ -17,8 +17,8 @@
> >
<a-input <a-input
name="name" name="name"
placeholder="" placeholder="类型名称"
v-decorator="['name', {rules: [{ required: true, message: '请输入资源'}]} ]" v-decorator="['name', {rules: [{ required: true, message: '请输入类型'}]} ]"
/> />
</a-form-item> </a-form-item>
@ -123,6 +123,7 @@ export default {
}, },
onClose () { onClose () {
this.form.resetFields() this.form.resetFields()
this.perms = []
this.drawerVisible = false this.drawerVisible = false
}, },
onChange (e) { onChange (e) {
@ -148,7 +149,7 @@ export default {
if (!err) { if (!err) {
console.log('Received values of form: ', values) console.log('Received values of form: ', values)
values.app_id = this.$store.state.app.name values.app_id = this.$route.name.split('_')[0]
values.perms = this.perms values.perms = this.perms
if (values.id) { if (values.id) {
this.updateResourceType(values.id, values) this.updateResourceType(values.id, values)

View File

@ -11,8 +11,8 @@
<a-form :form="form" :layout="formLayout" @submit="handleSubmit"> <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
<a-form-item <a-form-item
:label-col="formItemLayout.labelCol" :label-col="{span:6}"
:wrapper-col="formItemLayout.wrapperCol" :wrapper-col="{span:12}"
label="角色名" label="角色名"
> >
<a-input <a-input
@ -22,8 +22,20 @@
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label-col="horizontalFormItemLayout.labelCol" :label-col="{span:6}"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="{span:12}"
label="继承自"
>
<a-select
v-model="selectedParents"
placeholder="可选择继承角色"
mode="multiple">
<a-select-option v-for="role in allRoles" v-if="current_id !== role.id" :key="role.id">{{ role.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
:label-col="{span:8}"
:wrapper-col="{span:10}"
label="是否应用管理员" label="是否应用管理员"
> >
<a-switch <a-switch
@ -32,7 +44,6 @@
v-decorator="['is_app_admin', {rules: [], valuePropName: 'checked',} ]" v-decorator="['is_app_admin', {rules: [], valuePropName: 'checked',} ]"
/> />
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-input <a-input
name="id" name="id"
@ -50,12 +61,10 @@
borderTop: '1px solid #e9e9e9', borderTop: '1px solid #e9e9e9',
padding: '0.8rem 1rem', padding: '0.8rem 1rem',
background: '#fff', background: '#fff',
}" }"
> >
<a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button> <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">取消</a-button>
</div> </div>
</a-form> </a-form>
@ -65,7 +74,7 @@
<script> <script>
import { STable } from '@/components' import { STable } from '@/components'
import { addRole, updateRoleById } from '@/api/acl/role' import { addRole, updateRoleById, addParentRole, delParentRole } from '@/api/acl/role'
export default { export default {
name: 'RoleForm', name: 'RoleForm',
@ -75,8 +84,11 @@ export default {
data () { data () {
return { return {
drawerTitle: '新增角色', drawerTitle: '新增角色',
current_id: 0,
drawerVisible: false, drawerVisible: false,
formLayout: 'vertical' formLayout: 'vertical',
selectedParents: [],
oldParents: []
} }
}, },
@ -96,7 +108,7 @@ export default {
horizontalFormItemLayout () { horizontalFormItemLayout () {
return { return {
labelCol: { span: 5 }, labelCol: { span: 8 },
wrapperCol: { span: 12 } wrapperCol: { span: 12 }
} }
}, },
@ -113,10 +125,13 @@ export default {
methods: { methods: {
handleCreate () { handleCreate () {
this.drawerTitle = '新增'
this.drawerVisible = true this.drawerVisible = true
}, },
onClose () { onClose () {
this.form.resetFields() this.form.resetFields()
this.selectedParents = []
this.oldParents = []
this.drawerVisible = false this.drawerVisible = false
}, },
onChange (e) { onChange (e) {
@ -124,8 +139,16 @@ export default {
}, },
handleEdit (record) { handleEdit (record) {
this.drawerTitle = '编辑'
this.drawerVisible = true this.drawerVisible = true
console.log(record) this.current_id = record.id
const _parents = this.id2parents[record.id]
if (_parents) {
_parents.forEach(item => {
this.selectedParents.push(item.id)
this.oldParents.push(item.id)
})
}
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue({ this.form.setFieldsValue({
id: record.id, id: record.id,
@ -140,7 +163,7 @@ export default {
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
console.log('Received values of form: ', values) console.log('Received values of form: ', values)
values.app_id = this.$store.state.app.name values.app_id = this.$route.name.split('_')[0]
if (values.id) { if (values.id) {
this.updateRole(values.id, values) this.updateRole(values.id, values)
} else { } else {
@ -150,6 +173,7 @@ export default {
}) })
}, },
updateRole (id, data) { updateRole (id, data) {
this.updateParents(id)
updateRoleById(id, data) updateRoleById(id, data)
.then(res => { .then(res => {
this.$message.success(`更新成功`) this.$message.success(`更新成功`)
@ -162,12 +186,24 @@ export default {
addRole(data) addRole(data)
.then(res => { .then(res => {
this.$message.success(`添加成功`) this.$message.success(`添加成功`)
this.updateParents(res.id)
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
.catch(err => this.requestFailed(err)) .catch(err => this.requestFailed(err))
}, },
updateParents (id) {
this.oldParents.forEach(item => {
if (!this.selectedParents.includes(item)) {
delParentRole(id, item).catch(err => this.requestFailed(err))
}
})
this.selectedParents.forEach(item => {
if (!this.oldParents.includes(item)) {
addParentRole(id, item).catch(err => this.requestFailed(err))
}
})
},
requestFailed (err) { requestFailed (err) {
const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试' const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
this.$message.error(`${msg}`) this.$message.error(`${msg}`)
@ -179,6 +215,14 @@ export default {
handleOk: { handleOk: {
type: Function, type: Function,
default: null default: null
},
allRoles: {
type: Array,
required: true
},
id2parents: {
type: Object,
required: true
} }
} }

View File

@ -31,6 +31,17 @@
v-decorator="['nickname', {rules: []} ]" v-decorator="['nickname', {rules: []} ]"
/> />
</a-form-item> </a-form-item>
<a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
label="密码"
>
<a-input
type="password"
name="password"
v-decorator="['password', {rules: []} ]"
/>
</a-form-item>
<a-form-item <a-form-item
:label-col="formItemLayout.labelCol" :label-col="formItemLayout.labelCol"
@ -61,7 +72,21 @@
> >
<a-input <a-input
name="email" name="email"
v-decorator="['email', {rules: [{ required: true, message: '请输入邮箱'},{message: '请输入正确的邮箱', pattern: /^\w+\@\w+(\.\w+)+$/}]} ]" v-decorator="[
'email',
{
rules: [
{
type: 'email',
message: '请输入正确的邮箱!',
},
{
required: true,
message: '请输入邮箱',
},
],
},
]"
/> />
</a-form-item> </a-form-item>
@ -186,6 +211,7 @@ export default {
id: record.uid, id: record.uid,
username: record.username, username: record.username,
nickname: record.nickname, nickname: record.nickname,
password: record.password,
department: record.department, department: record.department,
catalog: record.catalog, catalog: record.catalog,
email: record.email, email: record.email,

View File

@ -149,7 +149,7 @@ export default {
} }
], ],
loadData: parameter => { loadData: parameter => {
parameter.app_id = this.$store.state.app.name parameter.app_id = this.$route.name.split('_')[0]
parameter.page = parameter.pageNo parameter.page = parameter.pageNo
parameter.page_size = parameter.pageSize parameter.page_size = parameter.pageSize
delete parameter.pageNo delete parameter.pageNo

View File

@ -130,13 +130,14 @@ export default {
{ {
title: '描述', title: '描述',
dataIndex: 'description', dataIndex: 'description',
width: 500, width: 200,
sorter: false, sorter: false,
scopedSlots: { customRender: 'description' } scopedSlots: { customRender: 'description' }
}, },
{ {
title: '权限', title: '权限',
dataIndex: 'id', dataIndex: 'id',
width: 300,
sorter: false, sorter: false,
scopedSlots: { customRender: 'id' } scopedSlots: { customRender: 'id' }
}, },
@ -144,12 +145,11 @@ export default {
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
width: 150, width: 150,
fixed: 'right',
scopedSlots: { customRender: 'action' } scopedSlots: { customRender: 'action' }
} }
], ],
loadData: parameter => { loadData: parameter => {
parameter.app_id = this.$store.state.app.name parameter.app_id = this.$route.name.split('_')[0]
parameter.page = parameter.pageNo parameter.page = parameter.pageNo
parameter.page_size = parameter.pageSize parameter.page_size = parameter.pageSize
delete parameter.pageNo delete parameter.pageNo

View File

@ -1,6 +1,22 @@
<template> <template>
<a-card :bordered="false"> <a-card :bordered="false">
<div>
<a-list :grid="{ gutter: 12, column: 12 }" style="height: 40px;clear: both">
<a-list-item
v-for="rtype in allResourceTypes"
:key="rtype.id"
:class="{'bottom-border':currentType.name===rtype.name}"
style="text-align: center;height: 30px; margin:0 30px"
>
<a
@click="loadCurrentType(rtype)"
:style="currentType.name === rtype.name?'color:#108ee9':'color:grey'">
<span style="font-size: 18px">{{ rtype.name }}</span>
</a>
</a-list-item>
</a-list>
</div>
<a-divider style="margin-top: -16px" />
<div class="action-btn"> <div class="action-btn">
<a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ btnName }}</a-button> <a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ btnName }}</a-button>
</div> </div>
@ -52,14 +68,11 @@
</span> </span>
<template v-else>{{ text }}</template> <template v-else>{{ text }}</template>
</template> </template>
<template slot="resource_type_id" slot-scope="text">{{ text }}</template>
<span slot="action" slot-scope="text, record"> <span slot="action" slot-scope="text, record">
<template> <template>
<a @click="handleEdit(record)">编辑</a> <a @click="handlePerm(record)">查看授权</a>
<a-divider type="vertical"/> <a-divider type="vertical"/>
<a @click="handlePerm(record)"></a> <a @click="handlePermManage(record)"></a>
<a-divider type="vertical"/> <a-divider type="vertical"/>
<a-popconfirm <a-popconfirm
title="确认删除?" title="确认删除?"
@ -74,9 +87,9 @@
</span> </span>
</s-table> </s-table>
<resourceForm ref="resourceForm" :handleOk="handleOk"> </resourceForm> <resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
<resourcePermForm ref="resourcePermForm"> </resourcePermForm> <resourcePermForm ref="resourcePermForm"> </resourcePermForm>
<ResourcePermManageForm ref="resourcePermManageForm" :groupTypeMessage="currentType"></ResourcePermManageForm>
</a-card> </a-card>
</template> </template>
@ -84,20 +97,23 @@
import { STable } from '@/components' import { STable } from '@/components'
import resourceForm from './module/resourceForm' import resourceForm from './module/resourceForm'
import resourcePermForm from './module/resourcePermForm' import resourcePermForm from './module/resourcePermForm'
import { deleteResourceById, searchResource } from '@/api/acl/resource' import ResourcePermManageForm from './module/resourcePermManageForm'
import { deleteResourceById, searchResource, searchResourceType } from '@/api/acl/resource'
export default { export default {
name: 'Index', name: 'Index',
components: { components: {
STable, STable,
resourceForm, resourceForm,
resourcePermForm resourcePermForm,
ResourcePermManageForm
}, },
data () { data () {
return { return {
scroll: { x: 1000, y: 500 }, scroll: { x: 1000, y: 500 },
btnName: '新增资源', btnName: '新增资源',
allResourceTypes: [],
currentType: { id: 0 },
formLayout: 'vertical', formLayout: 'vertical',
allResources: [], allResources: [],
@ -112,7 +128,7 @@ export default {
title: '资源名', title: '资源名',
dataIndex: 'name', dataIndex: 'name',
sorter: false, sorter: false,
width: 300, width: 250,
scopedSlots: { scopedSlots: {
customRender: 'nameSearchRender', customRender: 'nameSearchRender',
filterDropdown: 'filterDropdown', filterDropdown: 'filterDropdown',
@ -128,29 +144,30 @@ export default {
} }
}, },
{ {
title: '资源类型', title: '创建时间',
dataIndex: 'resource_type_id', width: 200,
sorter: false, dataIndex: 'created_at'
scopedSlots: { customRender: 'resource_type_id' } },
{
title: '最后修改时间',
width: 200,
dataIndex: 'updated_at'
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
width: 150, width: 150,
fixed: 'right',
scopedSlots: { customRender: 'action' } scopedSlots: { customRender: 'action' }
} }
], ],
loadData: parameter => { loadData: parameter => {
parameter.app_id = this.$store.state.app.name parameter.app_id = this.$route.name.split('_')[0]
parameter.page = parameter.pageNo parameter.page = parameter.pageNo
parameter.page_size = parameter.pageSize parameter.page_size = parameter.pageSize
parameter.resource_type_id = this.currentType.id
delete parameter.pageNo delete parameter.pageNo
delete parameter.pageSize delete parameter.pageSize
Object.assign(parameter, this.queryParam) Object.assign(parameter, this.queryParam)
console.log('loadData.parameter', parameter)
return searchResource(parameter) return searchResource(parameter)
.then(res => { .then(res => {
res.pageNo = res.page res.pageNo = res.page
@ -158,8 +175,6 @@ export default {
res.totalCount = res.numfound res.totalCount = res.numfound
res.totalPage = Math.ceil(res.numfound / parameter.pageSize) res.totalPage = Math.ceil(res.numfound / parameter.pageSize)
res.data = res.resources res.data = res.resources
console.log('loadData.res', res)
this.allResources = res.resources this.allResources = res.resources
return res return res
}) })
@ -190,7 +205,6 @@ export default {
}, },
computed: { computed: {
formItemLayout () { formItemLayout () {
const { formLayout } = this const { formLayout } = this
return formLayout === 'horizontal' ? { return formLayout === 'horizontal' ? {
@ -215,10 +229,26 @@ export default {
}, },
mounted () { mounted () {
this.setScrollY() this.setScrollY()
this.getAllResourceTypes()
}, },
inject: ['reload'], inject: ['reload'],
methods: { methods: {
getAllResourceTypes () {
searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then(res => {
this.allResourceTypes = res.groups
this.loadCurrentType(res.groups[0])
})
},
handlePermManage (record) {
this.$refs.resourcePermManageForm.editPerm(record)
},
loadCurrentType (rtype) {
if (rtype) {
this.currentType = rtype
}
this.$refs.table.refresh()
},
handleSearch (selectedKeys, confirm, column) { handleSearch (selectedKeys, confirm, column) {
confirm() confirm()
this.columnSearchText[column.dataIndex] = selectedKeys[0] this.columnSearchText[column.dataIndex] = selectedKeys[0]
@ -234,11 +264,6 @@ export default {
setScrollY () { setScrollY () {
this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 200 this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 200
}, },
handleEdit (record) {
this.$refs.resourceForm.handleEdit(record)
},
handlePerm (record) { handlePerm (record) {
this.$refs.resourcePermForm.handlePerm(record) this.$refs.resourcePermForm.handlePerm(record)
}, },
@ -250,7 +275,7 @@ export default {
}, },
handleCreate () { handleCreate () {
this.$refs.resourceForm.handleCreate() this.$refs.resourceForm.handleCreate(this.currentType)
}, },
deleteResource (id) { deleteResource (id) {
@ -284,6 +309,10 @@ export default {
width: calc(100% - 216px); width: calc(100% - 216px);
display: inline-block display: inline-block
} }
.bottom-border {
border-bottom: cornflowerblue 2px solid;
z-index: 1;
}
.operator { .operator {
margin-bottom: 18px; margin-bottom: 18px;

View File

@ -11,13 +11,11 @@
:data="loadData" :data="loadData"
:rowKey="record=>record.id" :rowKey="record=>record.id"
:rowSelection="options.rowSelection" :rowSelection="options.rowSelection"
:scroll="scroll"
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录`, pageSizeOptions: pageSizeOptions}" :pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录`, pageSizeOptions: pageSizeOptions}"
showPagination="auto" showPagination="auto"
:pageSize="25" :pageSize="25"
ref="table" ref="table"
size="middle" size="middle"
> >
<div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown"> <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
<a-input <a-input
@ -57,15 +55,13 @@
<a-icon type="check" v-if="text"/> <a-icon type="check" v-if="text"/>
</span> </span>
<span slot="id" slot-scope="key"> <span slot="inherit" slot-scope="key">
<a-tag color="cyan" v-for="role in id2parents[key]" :key="role.id">{{ role.name }}</a-tag> <a-tag color="cyan" v-for="role in id2parents[key]" :key="role.id">{{ role.name }}</a-tag>
</span> </span>
<span slot="action" slot-scope="text, record"> <span slot="action" slot-scope="text, record">
<template> <template>
<a @click="handleAddRoleRelation(record)">关联角色</a> <a @click="handleEdit(record)">修改</a>
<a-divider type="vertical"/>
<a @click="handleEdit(record)">编辑</a>
<a-divider type="vertical"/> <a-divider type="vertical"/>
<a-popconfirm <a-popconfirm
title="确认删除?" title="确认删除?"
@ -78,26 +74,21 @@
</a-popconfirm> </a-popconfirm>
</template> </template>
</span> </span>
</s-table> </s-table>
<roleForm ref="roleForm" :handleOk="handleOk"> </roleForm> <roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
<addRoleRelationForm ref="AddRoleRelationForm" :handleOk="handleOk"> </addRoleRelationForm>
</a-card> </a-card>
</template> </template>
<script> <script>
import { STable } from '@/components' import { STable } from '@/components'
import roleForm from './module/roleForm' import roleForm from './module/roleForm'
import AddRoleRelationForm from './module/addRoleRelationForm'
import { deleteRoleById, searchRole } from '@/api/acl/role' import { deleteRoleById, searchRole } from '@/api/acl/role'
export default { export default {
name: 'Index', name: 'Index',
components: { components: {
STable, STable,
roleForm, roleForm
AddRoleRelationForm
}, },
data () { data () {
return { return {
@ -119,7 +110,7 @@ export default {
title: '角色名', title: '角色名',
dataIndex: 'name', dataIndex: 'name',
sorter: false, sorter: false,
width: 250, width: 150,
scopedSlots: { scopedSlots: {
customRender: 'nameSearchRender', customRender: 'nameSearchRender',
filterDropdown: 'filterDropdown', filterDropdown: 'filterDropdown',
@ -135,7 +126,7 @@ export default {
} }
}, },
{ {
title: '管理者', title: '是否管理员',
dataIndex: 'is_app_admin', dataIndex: 'is_app_admin',
width: 100, width: 100,
sorter: false, sorter: false,
@ -143,22 +134,22 @@ export default {
}, },
{ {
title: '父角色', title: '继承自',
dataIndex: 'id', dataIndex: 'id',
sorter: false, sorter: false,
scopedSlots: { customRender: 'id' } width: 250,
scopedSlots: { customRender: 'inherit' }
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
width: 150, width: 150,
fixed: 'right',
scopedSlots: { customRender: 'action' } scopedSlots: { customRender: 'action' }
} }
], ],
loadData: parameter => { loadData: parameter => {
parameter.app_id = this.$store.state.app.name parameter.app_id = this.$route.name.split('_')[0]
parameter.page = parameter.pageNo parameter.page = parameter.pageNo
parameter.page_size = parameter.pageSize parameter.page_size = parameter.pageSize
delete parameter.pageNo delete parameter.pageNo
@ -200,11 +191,6 @@ export default {
} }
}, },
beforeCreate () {
this.form = this.$form.createForm(this)
},
computed: { computed: {
formItemLayout () { formItemLayout () {

View File

@ -17,7 +17,6 @@
:pageSize="25" :pageSize="25"
ref="table" ref="table"
size="middle" size="middle"
> >
<div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown"> <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
<a-input <a-input
@ -66,6 +65,9 @@
<span slot="is_check" slot-scope="text"> <span slot="is_check" slot-scope="text">
<a-icon type="check" v-if="text"/> <a-icon type="check" v-if="text"/>
</span> </span>
<span slot="block" slot-scope="text">
<a-icon type="lock" v-if="text"/>
</span>
<span slot="action" slot-scope="text, record"> <span slot="action" slot-scope="text, record">
<template> <template>
@ -103,7 +105,7 @@ export default {
}, },
data () { data () {
return { return {
scroll: { x: 1000, y: 500 }, scroll: { x: 1300, y: 500 },
btnName: '新增用户', btnName: '新增用户',
CITypeName: this.$route.params.CITypeName, CITypeName: this.$route.params.CITypeName,
@ -160,7 +162,7 @@ export default {
{ {
title: '部门', title: '部门',
dataIndex: 'department', dataIndex: 'department',
width: 200, width: 100,
sorter: false, sorter: false,
scopedSlots: { customRender: 'department' } scopedSlots: { customRender: 'department' }
@ -169,7 +171,7 @@ export default {
title: '小组', title: '小组',
dataIndex: 'catalog', dataIndex: 'catalog',
sorter: false, sorter: false,
width: 200, width: 100,
scopedSlots: { customRender: 'catalog' } scopedSlots: { customRender: 'catalog' }
}, },
@ -185,30 +187,28 @@ export default {
title: '手机', title: '手机',
dataIndex: 'mobile', dataIndex: 'mobile',
sorter: false, sorter: false,
width: 200, width: 150,
scopedSlots: { customRender: 'mobile' } scopedSlots: { customRender: 'mobile' }
},
{
title: '锁定',
dataIndex: 'block',
sorter: false,
width: 100,
scopedSlots: { customRender: 'block' }
}, },
{ {
title: '加入时间', title: '加入时间',
dataIndex: 'date_joined', dataIndex: 'date_joined',
sorter: false, sorter: false,
width: 200,
scopedSlots: { customRender: 'date_joined' } scopedSlots: { customRender: 'date_joined' }
}, },
{
title: '锁定',
dataIndex: 'block',
width: 100,
scopedSlots: { customRender: 'block' }
},
{ {
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
width: 150, width: 150,
fixed: 'right',
scopedSlots: { customRender: 'action' } scopedSlots: { customRender: 'action' }
} }
], ],
@ -233,17 +233,14 @@ export default {
return res return res
}) })
}, },
mdl: {}, mdl: {},
// 高级搜索 展开/关闭 // 高级搜索 展开/关闭
advanced: false, advanced: false,
// 查询参数 // 查询参数
queryParam: {}, queryParam: {},
// 表头 // 表头
selectedRowKeys: [], selectedRowKeys: [],
selectedRows: [], selectedRows: [],
// custom table alert & rowSelection // custom table alert & rowSelection
options: { options: {
alert: false, alert: false,
@ -253,11 +250,9 @@ export default {
} }
}, },
beforeCreate () { beforeCreate () {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
}, },
computed: { computed: {
formItemLayout () { formItemLayout () {
@ -343,12 +338,6 @@ export default {
.search { .search {
margin-bottom: 54px; margin-bottom: 54px;
} }
.fold {
width: calc(100% - 216px);
display: inline-block
}
.operator { .operator {
margin-bottom: 18px; margin-bottom: 18px;
} }
@ -361,14 +350,14 @@ export default {
background: #fff; background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, .15); box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
} }
.highlight { .highlight {
background-color: rgb(255, 192, 105); background-color: rgb(255, 192, 105);
padding: 0px; padding: 0;
} }
@media screen and (max-width: 900px) { .ant-table-body {
.fold { overflow: auto;
width: 100%; }
} .ant-table-content{
overflow: auto;
} }
</style> </style>