add action permissions
This commit is contained in:
@@ -13,22 +13,63 @@ export const MenuMapping = {
|
|||||||
userDomain: null,
|
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();
|
const store = useAuthStore();
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
app.directive('permissions', {
|
app.directive('permissions', {
|
||||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
const { value } = binding;
|
if (!hasPermission(binding.value)) {
|
||||||
if (!value || store.isSuperAdmin) {
|
hideElement(el);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
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;
|
isSeparator?: boolean;
|
||||||
target?:string;
|
target?:string;
|
||||||
disable?:boolean;
|
disable?:boolean;
|
||||||
permission?: string
|
permission?: string|null;
|
||||||
}
|
}
|
||||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
withDefaults(defineProps<EssentialLinkProps>(), {
|
||||||
caption: '',
|
caption: '',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<q-card-section class="q-mx-md " >
|
<q-card-section class="q-mx-md " >
|
||||||
<q-select
|
<q-select
|
||||||
|
v-permissions="props.actionPermissions.edit"
|
||||||
class="q-mt-md"
|
class="q-mt-md"
|
||||||
:disable="loading||!domain.domainActive"
|
:disable="loading||!domain.domainActive"
|
||||||
filled
|
filled
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:actions="{ row }">
|
<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>
|
</template>
|
||||||
</sharing-user-list>
|
</sharing-user-list>
|
||||||
|
|
||||||
@@ -76,7 +77,6 @@ import { useAuthStore } from 'stores/useAuthStore';
|
|||||||
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
||||||
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
|
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
|
||||||
import { Dialog } from 'quasar'
|
import { Dialog } from 'quasar'
|
||||||
import { IResponse } from 'src/types/BaseTypes';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ interface Props {
|
|||||||
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||||
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||||
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
|
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
|
||||||
|
actionPermissions: { 'list': string, 'edit': string };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUserDisplayWithShareRole extends IUserDisplay {
|
interface IUserDisplayWithShareRole extends IUserDisplay {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
:get-shared-api="getSharedApi"
|
:get-shared-api="getSharedApi"
|
||||||
:is-action-disable="(row) => row.id === authStore.userId"
|
:is-action-disable="(row) => row.id === authStore.userId"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
|
:action-permissions="Actions.domain.grantManage"
|
||||||
@update:modelValue="updateModelValue"
|
@update:modelValue="updateModelValue"
|
||||||
@close="close"
|
@close="close"
|
||||||
/>
|
/>
|
||||||
@@ -19,6 +20,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
|||||||
import { IUserDisplay } from '../../types/UserTypes';
|
import { IUserDisplay } from '../../types/UserTypes';
|
||||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { Actions } from 'boot/permissions';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
import { IResponse } from 'src/types/BaseTypes';
|
import { IResponse } from 'src/types/BaseTypes';
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
:remove-shared-api="removeSharedApi"
|
:remove-shared-api="removeSharedApi"
|
||||||
:get-shared-api="getSharedApi"
|
:get-shared-api="getSharedApi"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
|
:action-permissions="Actions.domain.grantUse"
|
||||||
@update:modelValue="updateModelValue"
|
@update:modelValue="updateModelValue"
|
||||||
@close="close"
|
@close="close"
|
||||||
/>
|
/>
|
||||||
@@ -18,6 +19,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
|||||||
import { IUserDisplay } from '../../types/UserTypes';
|
import { IUserDisplay } from '../../types/UserTypes';
|
||||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { Actions } from 'boot/permissions';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<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-menu :max-width="maxWidth">
|
||||||
<q-list dense :style="{ 'min-width': minWidth }">
|
<q-list dense :style="{ 'min-width': minWidth }">
|
||||||
<template v-for="(item, index) in actions" :key="index" >
|
<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)">
|
:class="item.class" clickable v-close-popup @click="item.action(row)">
|
||||||
<q-item-section side style="color: inherit;">
|
<q-item-section side style="color: inherit;">
|
||||||
<q-icon size="1.2em" :name="item.icon" />
|
<q-icon size="1.2em" :name="item.icon" />
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, Ref } from 'vue';
|
import { PropType, getCurrentInstance } from 'vue';
|
||||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
||||||
|
|
||||||
interface Action {
|
interface Action {
|
||||||
@@ -29,6 +29,7 @@ interface Action {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
tooltip?: string|((row: IDomainOwnerDisplay) => string);
|
tooltip?: string|((row: IDomainOwnerDisplay) => string);
|
||||||
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
|
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
|
||||||
|
permission?: string|object;
|
||||||
action: (row: any) => void|Promise<void>;
|
action: (row: any) => void|Promise<void>;
|
||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,18 @@ export default {
|
|||||||
|
|
||||||
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
|
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
|
||||||
return typeof item === 'function';
|
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',
|
icon: 'assignment_ind',
|
||||||
link: '/#/userDomain',
|
link: '/#/userDomain',
|
||||||
target: '_self',
|
target: '_self',
|
||||||
id: 'userDomain',
|
|
||||||
permission: MenuMapping.userDomain
|
permission: MenuMapping.userDomain
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const columns = [
|
|||||||
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
||||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||||
{ name: 'actions', label: '操作', field: 'actions', align: 'right' }
|
{ name: 'actions', label: '', field: 'actions', align: 'right' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const statusFilterOptions = [
|
const statusFilterOptions = [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||||
|
|
||||||
<template v-slot:top>
|
<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-space />
|
||||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
@@ -154,6 +154,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { Actions } from 'boot/permissions';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import ShareUsageDialog from 'components/ShareDomain/ShareUsageDialog.vue';
|
import ShareUsageDialog from 'components/ShareDomain/ShareUsageDialog.vue';
|
||||||
import ShareManageDialog from 'components/ShareDomain/ShareManageDialog.vue';
|
import ShareManageDialog from 'components/ShareDomain/ShareManageDialog.vue';
|
||||||
@@ -233,15 +234,16 @@ const SHARE_USE = 'use';
|
|||||||
const SHARE_MANAGE = 'manage';
|
const SHARE_MANAGE = 'manage';
|
||||||
|
|
||||||
const actionList = [
|
const actionList = [
|
||||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
{ label: '編集', icon: 'edit_note', permission: Actions.domain.edit, action: editRow },
|
||||||
{ label: '利用権限設定', icon: 'person_add_alt', action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
|
{ label: '利用権限設定', icon: 'person_add_alt', permission: Actions.domain.grantUse,
|
||||||
{ label: '管理権限設定', icon: 'add_moderator',
|
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
|
||||||
|
{ label: '管理権限設定', icon: 'add_moderator', permission: Actions.domain.grantManage,
|
||||||
disable: (row: IDomainOwnerDisplay) => !isOwner(row),
|
disable: (row: IDomainOwnerDisplay) => !isOwner(row),
|
||||||
tooltip: (row: IDomainOwnerDisplay) => isOwner(row) ? '' : 'ドメイン所有者でないため、操作できません',
|
tooltip: (row: IDomainOwnerDisplay) => isOwner(row) ? '' : 'ドメイン所有者でないため、操作できません',
|
||||||
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_MANAGE, row)}
|
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_MANAGE, row)}
|
||||||
},
|
},
|
||||||
{ separator: true },
|
{ 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);
|
const isOwner = (row: IDomainOwnerDisplay) => row.owner.id === Number(authStore.userId);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
:pagination="pagination" >
|
:pagination="pagination" >
|
||||||
|
|
||||||
<template v-slot:top>
|
<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-space />
|
||||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
@@ -161,6 +161,7 @@ import { ref, onMounted, computed } from 'vue';
|
|||||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||||
import { useUserStore } from 'stores/useUserStore';
|
import { useUserStore } from 'stores/useUserStore';
|
||||||
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||||
|
import { Actions } from 'boot/permissions';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
@@ -171,7 +172,7 @@ const columns = [
|
|||||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||||
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
|
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
{ name: 'actions', label: '', field: 'actions' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const statusFilterOptions = [
|
const statusFilterOptions = [
|
||||||
@@ -205,9 +206,9 @@ const editId = ref(0);
|
|||||||
const isCreate = computed(() => editId.value <= 0);
|
const isCreate = computed(() => editId.value <= 0);
|
||||||
|
|
||||||
const actionList = [
|
const actionList = [
|
||||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
{ label: '編集', icon: 'edit_note', permission: Actions.user.edit, action: editRow },
|
||||||
{ separator: true },
|
{ 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 () => {
|
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>;
|
const component: DefineComponent<{}, {}, any>;
|
||||||
export default component;
|
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