add action permissions
This commit is contained in:
@@ -13,22 +13,63 @@ export const MenuMapping = {
|
||||
userDomain: null,
|
||||
};
|
||||
|
||||
export const Actions = {
|
||||
user: {
|
||||
show: 'user_list',
|
||||
add: 'user_add',
|
||||
edit: 'user_edit',
|
||||
delete: 'user_delete',
|
||||
},
|
||||
role: {
|
||||
show: 'role_list',
|
||||
},
|
||||
domain: {
|
||||
show: 'domain_list',
|
||||
add: 'domain_add',
|
||||
edit: 'domain_edit',
|
||||
delete: 'domain_delete',
|
||||
grantUse: {
|
||||
list: 'domain_grant_use_list',
|
||||
edit: 'domain_grant_use_edit',
|
||||
},
|
||||
grantManage: {
|
||||
list: 'domain_grant_manage_list',
|
||||
edit: 'domain_grant_manage_edit',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = useAuthStore();
|
||||
|
||||
export default boot(({ app }) => {
|
||||
app.directive('permissions', {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
if (!value || store.isSuperAdmin) {
|
||||
return;
|
||||
if (!hasPermission(binding.value)) {
|
||||
hideElement(el);
|
||||
}
|
||||
if (!store.permissions[value]) {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.config.globalProperties.$hasPermission = hasPermission;
|
||||
});
|
||||
|
||||
function hasPermission(value: any) {
|
||||
if (!value || store.isSuperAdmin) {
|
||||
return true;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return store.permissions[value];
|
||||
} else if (typeof value === 'object') {
|
||||
return Object.values(value).some((permission: any) => store.permissions[permission]);
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.some((permission) => store.permissions[permission]);
|
||||
}
|
||||
}
|
||||
|
||||
function hideElement(el: HTMLElement) {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export interface EssentialLinkProps {
|
||||
isSeparator?: boolean;
|
||||
target?:string;
|
||||
disable?:boolean;
|
||||
permission?: string
|
||||
permission?: string|null;
|
||||
}
|
||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
||||
caption: '',
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<q-card-section class="q-mx-md " >
|
||||
<q-select
|
||||
v-permissions="props.actionPermissions.edit"
|
||||
class="q-mt-md"
|
||||
:disable="loading||!domain.domainActive"
|
||||
filled
|
||||
@@ -53,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<template v-slot:actions="{ row }">
|
||||
<q-btn round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
|
||||
<q-btn v-permissions="props.actionPermissions.edit" round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
|
||||
</template>
|
||||
</sharing-user-list>
|
||||
|
||||
@@ -76,7 +77,6 @@ import { useAuthStore } from 'stores/useAuthStore';
|
||||
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
||||
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
|
||||
import { Dialog } from 'quasar'
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@@ -89,6 +89,7 @@ interface Props {
|
||||
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
actionPermissions: { 'list': string, 'edit': string };
|
||||
}
|
||||
|
||||
interface IUserDisplayWithShareRole extends IUserDisplay {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:get-shared-api="getSharedApi"
|
||||
:is-action-disable="(row) => row.id === authStore.userId"
|
||||
:model-value="modelValue"
|
||||
:action-permissions="Actions.domain.grantManage"
|
||||
@update:modelValue="updateModelValue"
|
||||
@close="close"
|
||||
/>
|
||||
@@ -19,6 +20,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import { Actions } from 'boot/permissions';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:remove-shared-api="removeSharedApi"
|
||||
:get-shared-api="getSharedApi"
|
||||
:model-value="modelValue"
|
||||
:action-permissions="Actions.domain.grantUse"
|
||||
@update:modelValue="updateModelValue"
|
||||
@close="close"
|
||||
/>
|
||||
@@ -18,6 +19,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import { Actions } from 'boot/permissions';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<q-btn flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
|
||||
<q-btn v-if="hasPermission()" flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
|
||||
<q-menu :max-width="maxWidth">
|
||||
<q-list dense :style="{ 'min-width': minWidth }">
|
||||
<template v-for="(item, index) in actions" :key="index" >
|
||||
<q-item v-if="isAction(item)" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
|
||||
<q-item v-if="isAction(item)" v-permissions="item.permission" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
|
||||
:class="item.class" clickable v-close-popup @click="item.action(row)">
|
||||
<q-item-section side style="color: inherit;">
|
||||
<q-icon size="1.2em" :name="item.icon" />
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, Ref } from 'vue';
|
||||
import { PropType, getCurrentInstance } from 'vue';
|
||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
||||
|
||||
interface Action {
|
||||
@@ -29,6 +29,7 @@ interface Action {
|
||||
icon?: string;
|
||||
tooltip?: string|((row: IDomainOwnerDisplay) => string);
|
||||
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
|
||||
permission?: string|object;
|
||||
action: (row: any) => void|Promise<void>;
|
||||
class?: string;
|
||||
}
|
||||
@@ -64,6 +65,18 @@ export default {
|
||||
|
||||
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
|
||||
return typeof item === 'function';
|
||||
},
|
||||
|
||||
hasPermission() {
|
||||
const proxy = getCurrentInstance()?.proxy;
|
||||
if (!proxy) {
|
||||
return false;
|
||||
}
|
||||
for (const item of this.actions) {
|
||||
if (this.isAction(item) && proxy.$hasPermission(item.permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,7 +111,6 @@ const essentialLinks: EssentialLinkProps[] = reactive([
|
||||
icon: 'assignment_ind',
|
||||
link: '/#/userDomain',
|
||||
target: '_self',
|
||||
id: 'userDomain',
|
||||
permission: MenuMapping.userDomain
|
||||
},
|
||||
// {
|
||||
|
||||
@@ -120,7 +120,7 @@ const columns = [
|
||||
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||
{ name: 'actions', label: '操作', field: 'actions', align: 'right' }
|
||||
{ name: 'actions', label: '', field: 'actions', align: 'right' }
|
||||
];
|
||||
|
||||
const statusFilterOptions = [
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-btn v-permissions="Actions.domain.add" color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
@@ -154,6 +154,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { Actions } from 'boot/permissions';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import ShareUsageDialog from 'components/ShareDomain/ShareUsageDialog.vue';
|
||||
import ShareManageDialog from 'components/ShareDomain/ShareManageDialog.vue';
|
||||
@@ -233,15 +234,16 @@ const SHARE_USE = 'use';
|
||||
const SHARE_MANAGE = 'manage';
|
||||
|
||||
const actionList = [
|
||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
||||
{ label: '利用権限設定', icon: 'person_add_alt', action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
|
||||
{ label: '管理権限設定', icon: 'add_moderator',
|
||||
{ label: '編集', icon: 'edit_note', permission: Actions.domain.edit, action: editRow },
|
||||
{ label: '利用権限設定', icon: 'person_add_alt', permission: Actions.domain.grantUse,
|
||||
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
|
||||
{ label: '管理権限設定', icon: 'add_moderator', permission: Actions.domain.grantManage,
|
||||
disable: (row: IDomainOwnerDisplay) => !isOwner(row),
|
||||
tooltip: (row: IDomainOwnerDisplay) => isOwner(row) ? '' : 'ドメイン所有者でないため、操作できません',
|
||||
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_MANAGE, row)}
|
||||
},
|
||||
{ separator: true },
|
||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
||||
{ label: '削除', icon: 'delete_outline', permission: Actions.domain.delete, class: 'text-red', action: removeRow },
|
||||
];
|
||||
|
||||
const isOwner = (row: IDomainOwnerDisplay) => row.owner.id === Number(authStore.userId);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:pagination="pagination" >
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-btn v-permissions="Actions.user.add" color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
@@ -161,6 +161,7 @@ import { ref, onMounted, computed } from 'vue';
|
||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||
import { useUserStore } from 'stores/useUserStore';
|
||||
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||
import { Actions } from 'boot/permissions';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -171,7 +172,7 @@ const columns = [
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
{ name: 'actions', label: '', field: 'actions' }
|
||||
];
|
||||
|
||||
const statusFilterOptions = [
|
||||
@@ -205,9 +206,9 @@ const editId = ref(0);
|
||||
const isCreate = computed(() => editId.value <= 0);
|
||||
|
||||
const actionList = [
|
||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
||||
{ label: '編集', icon: 'edit_note', permission: Actions.user.edit, action: editRow },
|
||||
{ separator: true },
|
||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: showDeleteUserConfirm },
|
||||
{ label: '削除', icon: 'delete_outline', permission: Actions.user.delete, class: 'text-red', action: showDeleteUserConfirm },
|
||||
];
|
||||
|
||||
const getUsers = async () => {
|
||||
|
||||
8
frontend/src/shims-vue.d.ts
vendored
8
frontend/src/shims-vue.d.ts
vendored
@@ -8,3 +8,11 @@ declare module '*.vue' {
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
import { ComponentCustomProperties } from 'vue';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$hasPermission: (permission: any) => boolean;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user