feat: refine ui permissions (#628)

#### What type of PR is this?

/kind feature
/kind improvement
/milestone 2.0

#### What this PR does / why we need it:

完善 UI 权限控制。适配 https://github.com/halo-dev/halo/pull/2488

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/2342

#### Special notes for your reviewer:

/cc @halo-dev/sig-halo-console 

测试方式:

1. Halo 需要使用 https://github.com/halo-dev/halo/issues/2342 PR 的分支。
2. 创建新的角色,并勾选需要测试的权限。
3. 创建新的用户并赋予新的角色。
4. 测试操作权限。


#### Does this PR introduce a user-facing change?

```release-note
完善 UI 权限控制
```
pull/633/head
Ryan Wang 2022-09-30 17:48:19 +08:00 committed by GitHub
parent a6e913abc5
commit 3ae432ac75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 278 additions and 58 deletions

View File

@ -173,9 +173,10 @@ async function loadCurrentUser() {
app.directive(
"permission",
(el: HTMLElement, binding: DirectiveBinding<string[]>) => {
const uiPermissions = Array.from<string>(
currentPermissions.uiPermissions
);
// const uiPermissions = Array.from<string>(
// currentPermissions.uiPermissions
// );
const uiPermissions = Array.from<string>(["system:attachments:view"]);
const { value } = binding;
const { any, enable } = binding.modifiers;

View File

@ -38,6 +38,9 @@ import cloneDeep from "lodash.clonedeep";
import { isImage } from "@/utils/image";
import { useRouteQuery } from "@vueuse/router";
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const policyVisible = ref(false);
const uploadVisible = ref(false);
@ -233,13 +236,21 @@ onMounted(() => {
</template>
<template #actions>
<VSpace>
<VButton size="sm" @click="policyVisible = true">
<VButton
v-permission="['system:attachments:manage']"
size="sm"
@click="policyVisible = true"
>
<template #icon>
<IconDatabase2Line class="h-full w-full" />
</template>
存储策略
</VButton>
<VButton type="secondary" @click="uploadVisible = true">
<VButton
v-permission="['system:attachments:manage']"
type="secondary"
@click="uploadVisible = true"
>
<template #icon>
<IconUpload class="h-full w-full" />
</template>
@ -258,7 +269,10 @@ onMounted(() => {
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="mr-4 hidden items-center sm:flex">
<div
v-permission="['system:attachments:manage']"
class="mr-4 hidden items-center sm:flex"
>
<input
v-model="checkedAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
@ -489,7 +503,11 @@ onMounted(() => {
<template #actions>
<VSpace>
<VButton @click="handleFetchAttachments"></VButton>
<VButton type="secondary" @click="uploadVisible = true">
<VButton
v-permission="['system:attachments:manage']"
type="secondary"
@click="uploadVisible = true"
>
<template #icon>
<IconUpload class="h-full w-full" />
</template>
@ -565,6 +583,7 @@ onMounted(() => {
<div
v-if="!attachment.metadata.deletionTimestamp"
v-permission="['system:attachments:manage']"
:class="{ '!flex': selectedAttachments.has(attachment) }"
class="absolute top-0 left-0 hidden h-1/3 w-full cursor-pointer justify-end bg-gradient-to-b from-gray-300 to-transparent ease-in-out group-hover:flex"
>
@ -588,7 +607,12 @@ onMounted(() => {
>
<li v-for="(attachment, index) in attachments.items" :key="index">
<VEntity :is-selected="isChecked(attachment)">
<template #checkbox>
<template
v-if="
!currentUserHasPermission(['system:attachments:manage'])
"
#checkbox
>
<input
:checked="selectedAttachments.has(attachment)"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
@ -669,7 +693,12 @@ onMounted(() => {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="
!currentUserHasPermission(['system:attachments:manage'])
"
#dropdownItems
>
<VButton
v-close-popper
block

View File

@ -132,7 +132,10 @@ onMounted(async () => {
{{ group.spec.displayName }}
</span>
</div>
<FloatingDropdown v-if="!readonly">
<FloatingDropdown
v-if="!readonly"
v-permission="['system:attachments:manage']"
>
<IconMore @click.stop />
<template #popper>
<div class="w-48 p-2">
@ -153,6 +156,7 @@ onMounted(async () => {
</div>
<div
v-if="!loading && !readonly"
v-permission="['system:attachments:manage']"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
@click="editingModal = true"
>

View File

@ -17,6 +17,7 @@ export default definePlugin({
component: AttachmentList,
meta: {
title: "附件",
permissions: ["system:attachments:view"],
},
},
],

View File

@ -241,7 +241,10 @@ function handleSelectUser(user: User | undefined) {
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="mr-4 hidden items-center sm:flex">
<div
v-permission="['system:comments:manage']"
class="mr-4 hidden items-center sm:flex"
>
<input
v-model="checkAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"

View File

@ -25,6 +25,9 @@ import ReplyListItem from "./ReplyListItem.vue";
import { apiClient } from "@/utils/api-client";
import type { RouteLocationRaw } from "vue-router";
import cloneDeep from "lodash.clonedeep";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const props = withDefaults(
defineProps<{
@ -247,7 +250,10 @@ const subjectRefResult = computed(() => {
<div class="absolute inset-x-0 top-0 h-[1px] bg-black/50"></div>
<div class="absolute inset-x-0 bottom-0 h-[1px] bg-black/50"></div>
</template>
<template #checkbox>
<template
v-if="!currentUserHasPermission(['system:comments:manage'])"
#checkbox
>
<slot name="checkbox" />
</template>
<template #start>
@ -336,7 +342,10 @@ const subjectRefResult = computed(() => {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:comments:manage'])"
#dropdownItems
>
<VButton
v-if="!comment?.comment.spec.approved"
v-close-popper

View File

@ -172,6 +172,7 @@ const isHoveredReply = computed(() => {
<template #dropdownItems>
<VButton
v-if="!reply?.reply.spec.approved"
v-permission="['system:comments:manage']"
v-close-popper
type="secondary"
block
@ -179,7 +180,13 @@ const isHoveredReply = computed(() => {
>
审核通过
</VButton>
<VButton v-close-popper block type="danger" @click="handleDelete">
<VButton
v-permission="['system:comments:manage']"
v-close-popper
block
type="danger"
@click="handleDelete"
>
删除
</VButton>
</template>

View File

@ -17,6 +17,7 @@ export default definePlugin({
meta: {
title: "评论",
searchable: true,
permissions: ["system:comments:view"],
},
},
],

View File

@ -32,6 +32,9 @@ import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date";
import { RouterLink } from "vue-router";
import cloneDeep from "lodash.clonedeep";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
enum SinglePagePhase {
DRAFT = "未发布",
@ -327,7 +330,10 @@ function handleSortItemChange(sortItem?: SortItem) {
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="mr-4 hidden items-center sm:flex">
<div
v-permission="['system:singlepages:manage']"
class="mr-4 hidden items-center sm:flex"
>
<input
v-model="checkAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
@ -509,7 +515,11 @@ function handleSortItemChange(sortItem?: SortItem) {
<template #actions>
<VSpace>
<VButton @click="handleFetchSinglePages"></VButton>
<VButton :route="{ name: 'SinglePageEditor' }" type="primary">
<VButton
v-permission="['system:singlepages:manage']"
:route="{ name: 'SinglePageEditor' }"
type="primary"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -525,7 +535,10 @@ function handleSortItemChange(sortItem?: SortItem) {
>
<li v-for="(singlePage, index) in singlePages.items" :key="index">
<VEntity :is-selected="checkAll">
<template #checkbox>
<template
v-if="!currentUserHasPermission(['system:singlepages:manage'])"
#checkbox
>
<input
v-model="checkAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
@ -622,7 +635,10 @@ function handleSortItemChange(sortItem?: SortItem) {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:singlepages:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block

View File

@ -70,7 +70,11 @@ watchEffect(() => {
<IconPages class="mr-2 self-center" />
</template>
<template #actions>
<VButton :route="{ name: 'SinglePageEditor' }" type="secondary">
<VButton
v-permission="['system:singlepages:manage']"
:route="{ name: 'SinglePageEditor' }"
type="secondary"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>

View File

@ -47,6 +47,7 @@ export default definePlugin({
meta: {
title: "自定义页面",
searchable: true,
permissions: ["system:singlepages:view"],
},
},
],
@ -62,6 +63,7 @@ export default definePlugin({
meta: {
title: "页面编辑",
searchable: true,
permissions: ["system:singlepages:manage"],
},
},
],

View File

@ -37,6 +37,9 @@ import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date";
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
enum PostPhase {
DRAFT = "未发布",
@ -398,7 +401,11 @@ function handleContributorChange(user?: User) {
<VSpace>
<VButton :route="{ name: 'Categories' }" size="sm">分类</VButton>
<VButton :route="{ name: 'Tags' }" size="sm">标签</VButton>
<VButton :route="{ name: 'PostEditor' }" type="secondary">
<VButton
v-permission="['system:posts:manage']"
:route="{ name: 'PostEditor' }"
type="secondary"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -415,7 +422,10 @@ function handleContributorChange(user?: User) {
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="mr-4 hidden items-center sm:flex">
<div
v-permission="['system:posts:manage']"
class="mr-4 hidden items-center sm:flex"
>
<input
v-model="checkedAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
@ -769,7 +779,11 @@ function handleContributorChange(user?: User) {
<template #actions>
<VSpace>
<VButton @click="handleFetchPosts"></VButton>
<VButton :route="{ name: 'PostEditor' }" type="primary">
<VButton
v-permission="['system:posts:manage']"
:route="{ name: 'PostEditor' }"
type="primary"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -785,7 +799,10 @@ function handleContributorChange(user?: User) {
>
<li v-for="(post, index) in posts.items" :key="index">
<VEntity :is-selected="checkSelection(post.post)">
<template #checkbox>
<template
v-if="!currentUserHasPermission(['system:posts:manage'])"
#checkbox
>
<input
v-model="selectedPostNames"
:value="post.post.metadata.name"
@ -904,7 +921,10 @@ function handleContributorChange(user?: User) {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:posts:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block

View File

@ -84,7 +84,11 @@ const onEditingModalClose = () => {
</template>
<template #actions>
<VButton type="secondary" @click="editingModal = true">
<VButton
v-permission="['system:posts:manage']"
type="secondary"
@click="editingModal = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -115,7 +119,11 @@ const onEditingModalClose = () => {
<template #actions>
<VSpace>
<VButton @click="handleFetchCategories"></VButton>
<VButton type="primary" @click="editingModal = true">
<VButton
v-permission="['system:posts:manage']"
type="primary"
@click="editingModal = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>

View File

@ -58,6 +58,7 @@ function onDelete(category: CategoryTree) {
<VEntity>
<template #prepend>
<div
v-permission="['system:posts:manage']"
class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex"
>
<IconList class="h-3.5 w-3.5" />
@ -88,6 +89,7 @@ function onDelete(category: CategoryTree) {
</template>
<template #dropdownItems>
<VButton
v-permission="['system:posts:manage']"
v-close-popper
block
type="secondary"
@ -96,6 +98,7 @@ function onDelete(category: CategoryTree) {
修改
</VButton>
<VButton
v-permission="['system:posts:manage']"
v-close-popper
block
type="danger"

View File

@ -20,6 +20,7 @@ export default definePlugin({
meta: {
title: "文章",
searchable: true,
permissions: ["system:posts:view"],
},
},
{
@ -29,6 +30,7 @@ export default definePlugin({
meta: {
title: "文章编辑",
searchable: true,
permissions: ["system:posts:manage"],
},
},
{
@ -42,6 +44,7 @@ export default definePlugin({
meta: {
title: "文章分类",
searchable: true,
permissions: ["system:posts:view"],
},
},
],
@ -57,6 +60,7 @@ export default definePlugin({
meta: {
title: "文章标签",
searchable: true,
permissions: ["system:posts:view"],
},
},
],

View File

@ -115,7 +115,11 @@ onMounted(async () => {
<IconBookRead class="mr-2 self-center" />
</template>
<template #actions>
<VButton type="secondary" @click="editingModal = true">
<VButton
v-permission="['system:posts:manage']"
type="secondary"
@click="editingModal = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -204,6 +208,7 @@ onMounted(async () => {
</template>
<template #dropdownItems>
<VButton
v-permission="['system:posts:manage']"
v-close-popper
block
type="secondary"
@ -212,6 +217,7 @@ onMounted(async () => {
修改
</VButton>
<VButton
v-permission="['system:posts:manage']"
v-close-popper
block
type="danger"

View File

@ -8,24 +8,35 @@ import {
IconUserSettings,
IconPalette,
VCard,
IconUserLine,
} from "@halo-dev/components";
import { markRaw, type Component } from "vue";
import { inject, markRaw, type Component } from "vue";
import { useRouter } from "vue-router";
import type { RouteLocationRaw } from "vue-router";
import type { User } from "@halo-dev/api-client";
interface Action {
icon: Component;
title: string;
route: RouteLocationRaw;
permissions?: string[];
}
const currentUser = inject<User>("currentUser");
const actions: Action[] = [
{
icon: markRaw(IconUserLine),
title: "个人资料",
route: { name: "UserDetail", params: { name: currentUser?.metadata.name } },
},
{
icon: markRaw(IconBookRead),
title: "创建文章",
route: {
name: "PostEditor",
},
permissions: ["system:posts:manage"],
},
{
icon: markRaw(IconPages),
@ -33,6 +44,7 @@ const actions: Action[] = [
route: {
name: "SinglePageEditor",
},
permissions: ["system:singlepages:manage"],
},
{
icon: markRaw(IconFolder),
@ -43,6 +55,7 @@ const actions: Action[] = [
action: "upload",
},
},
permissions: ["system:attachments:manage"],
},
{
icon: markRaw(IconPalette),
@ -50,6 +63,7 @@ const actions: Action[] = [
route: {
name: "ThemeDetail",
},
permissions: ["system:themes:view"],
},
{
icon: markRaw(IconPlug),
@ -57,6 +71,7 @@ const actions: Action[] = [
route: {
name: "Plugins",
},
permissions: ["system:plugins:view"],
},
{
icon: markRaw(IconUserSettings),
@ -67,6 +82,7 @@ const actions: Action[] = [
action: "create",
},
},
permissions: ["system:users:manage"],
},
];
@ -82,6 +98,7 @@ const router = useRouter();
<div
v-for="(action, index) in actions"
:key="index"
v-permission="action.permissions"
class="group relative cursor-pointer bg-white p-6 transition-all hover:bg-gray-50"
@click="router.push(action.route)"
>

View File

@ -175,6 +175,7 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
<div class="mt-4 flex sm:mt-0">
<VSpace>
<VButton
v-permission="['system:menus:manage']"
size="xs"
type="default"
@click="menuItemEditingModal = true"
@ -194,7 +195,11 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
<template #actions>
<VSpace>
<VButton @click="handleFetchMenuItems"> </VButton>
<VButton type="primary" @click="menuItemEditingModal = true">
<VButton
v-permission="['system:menus:manage']"
type="primary"
@click="menuItemEditingModal = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>

View File

@ -12,11 +12,11 @@ import { setFocus } from "@/formkit/utils/focus";
const props = withDefaults(
defineProps<{
visible: boolean;
menu: Menu | null;
menu?: Menu;
}>(),
{
visible: false,
menu: null,
menu: undefined,
}
);

View File

@ -10,6 +10,9 @@ import {
import Draggable from "vuedraggable";
import { ref } from "vue";
import type { MenuTreeItem } from "@/modules/interface/menus/utils";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
withDefaults(
defineProps<{
@ -74,6 +77,7 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
<VEntity>
<template #prepend>
<div
v-permission="['system:menus:manage']"
class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex"
>
<IconList class="h-3.5 w-3.5" />
@ -98,7 +102,10 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:menus:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block

View File

@ -14,6 +14,9 @@ import { defineExpose, onMounted, ref } from "vue";
import type { Menu } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client";
import { useRouteQuery } from "@vueuse/router";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const props = withDefaults(
defineProps<{
@ -31,13 +34,13 @@ const emit = defineEmits<{
const menus = ref<Menu[]>([] as Menu[]);
const loading = ref(false);
const selectedMenuToUpdate = ref<Menu | null>(null);
const selectedMenuToUpdate = ref<Menu>();
const menuEditingModal = ref<boolean>(false);
const dialog = useDialog();
const handleFetchMenus = async () => {
selectedMenuToUpdate.value = null;
selectedMenuToUpdate.value = undefined;
try {
loading.value = true;
@ -95,7 +98,7 @@ const handleDeleteMenu = async (menu: Menu) => {
});
};
const handleOpenEditingModal = (menu: Menu | null) => {
const handleOpenEditingModal = (menu?: Menu) => {
selectedMenuToUpdate.value = menu;
menuEditingModal.value = true;
};
@ -161,7 +164,10 @@ defineExpose({
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:menus:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
@ -182,8 +188,8 @@ defineExpose({
</VEntity>
</li>
</ul>
<template #footer>
<VButton block type="secondary" @click="handleOpenEditingModal(null)">
<template v-if="!currentUserHasPermission(['system:menus:manage'])" #footer>
<VButton block type="secondary" @click="handleOpenEditingModal()">
新增
</VButton>
</template>

View File

@ -17,6 +17,7 @@ export default definePlugin({
meta: {
title: "菜单",
searchable: true,
permissions: ["system:menus:view"],
},
},
],

View File

@ -57,7 +57,7 @@ const handleReloadThemeSetting = async () => {
</p>
</div>
</div>
<FloatingDropdown>
<FloatingDropdown v-permission="['system:themes:manage']">
<div
class="cursor-pointer rounded p-1 transition-all hover:text-blue-600 group-hover:bg-gray-100"
>

View File

@ -17,6 +17,9 @@ import ThemeInstallModal from "./ThemeInstallModal.vue";
import { ref, watch } from "vue";
import type { Theme } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const props = withDefaults(
defineProps<{
@ -141,7 +144,11 @@ defineExpose({
<template #actions>
<VSpace>
<VButton @click="handleFetchThemes"> </VButton>
<VButton type="primary" @click="themeInstall = true">
<VButton
v-permission="['system:themes:manage']"
type="primary"
@click="themeInstall = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
@ -238,7 +245,10 @@ defineExpose({
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:themes:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
@ -262,7 +272,11 @@ defineExpose({
</ul>
<template #footer>
<VSpace>
<VButton type="secondary" @click="themeInstall = true">
<VButton
v-permission="['system:themes:manage']"
type="secondary"
@click="themeInstall = true"
>
安装主题
</VButton>
<VButton @click="onVisibleChange(false)"></VButton>

View File

@ -156,6 +156,7 @@ watch([() => route.name, () => route.params], async () => {
</VButton>
<VButton
v-if="!isActivated"
v-permission="['system:themes:manage']"
size="sm"
type="primary"
@click="handleActiveTheme"

View File

@ -20,6 +20,7 @@ export default definePlugin({
meta: {
title: "主题",
searchable: true,
permissions: ["system:themes:view"],
},
},
{
@ -28,6 +29,7 @@ export default definePlugin({
component: ThemeSetting,
meta: {
title: "主题设置",
permissions: ["system:themes:view"],
},
},
],

View File

@ -12,6 +12,9 @@ import { toRefs } from "vue";
import { usePluginLifeCycle } from "../composables/use-plugin";
import type { Plugin } from "@halo-dev/api-client";
import { formatDatetime } from "@/utils/date";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const props = withDefaults(
defineProps<{
@ -86,12 +89,9 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
</span>
</template>
</VEntityField>
<VEntityField>
<VEntityField v-permission="['system:plugins:manage']">
<template #description>
<div
v-permission="['system:plugins:manage']"
class="flex items-center"
>
<div class="flex items-center">
<VSwitch
:model-value="plugin?.spec.enabled"
@click="changeStatus"
@ -100,7 +100,10 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:plugins:manage'])"
#dropdownItems
>
<VButton v-close-popper block type="danger" @click="uninstall">
卸载
</VButton>

View File

@ -32,6 +32,9 @@ import { useFetchRole } from "@/modules/system/roles/composables/use-role";
import cloneDeep from "lodash.clonedeep";
import { apiClient } from "@/utils/api-client";
import Fuse from "fuse.js";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const editingModal = ref<boolean>(false);
const selectedRole = ref<Role>();
@ -272,7 +275,10 @@ const handleDelete = async (role: Role) => {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:roles:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block

View File

@ -26,6 +26,9 @@ import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date";
import { useRouteQuery } from "@vueuse/router";
import Fuse from "fuse.js";
import { usePermission } from "@/utils/permission";
const { currentUserHasPermission } = usePermission();
const dialog = useDialog();
@ -254,10 +257,12 @@ onMounted(() => {
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="mr-4 hidden items-center sm:flex">
<div
v-permission="['system:users:manage']"
class="mr-4 hidden items-center sm:flex"
>
<input
v-model="checkedAll"
v-permission="['system:users:manage']"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox"
@change="handleCheckAllChange"
@ -382,10 +387,12 @@ onMounted(() => {
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li v-for="(user, index) in searchResults" :key="index">
<VEntity :is-selected="checkSelection(user)">
<template #checkbox>
<template
v-if="!currentUserHasPermission(['system:users:manage'])"
#checkbox
>
<input
v-model="selectedUserNames"
v-permission="['system:users:manage']"
:value="user.metadata.name"
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
name="post-checkbox"
@ -438,10 +445,12 @@ onMounted(() => {
</template>
</VEntityField>
</template>
<template #dropdownItems>
<template
v-if="!currentUserHasPermission(['system:users:manage'])"
#dropdownItems
>
<VButton
v-close-popper
v-permission="['system:users:manage']"
block
type="secondary"
@click="handleOpenCreateModal(user)"
@ -450,7 +459,6 @@ onMounted(() => {
</VButton>
<VButton
v-close-popper
v-permission="['system:users:manage']"
block
@click="handleOpenPasswordChangeModal(user)"
>
@ -459,7 +467,6 @@ onMounted(() => {
<VButton
v-if="currentUser?.metadata.name !== user.metadata.name"
v-close-popper
v-permission="['system:users:manage']"
block
@click="handleOpenGrantPermissionModal(user)"
>
@ -468,7 +475,6 @@ onMounted(() => {
<VButton
v-if="currentUser?.metadata.name !== user.metadata.name"
v-close-popper
v-permission="['system:users:manage']"
block
type="danger"
@click="handleDelete(user)"

View File

@ -1,5 +1,15 @@
import { useRoleStore } from "@/stores/role";
import isEqual from "lodash.isequal";
/**
* It returns true if the user has all the permissions required to access a resource
*
* @param uiPermissions - The permissions that the user has.
* @param targetPermissions - The permissions that the user needs to have in order to access the
* resource.
* @param {boolean} any - boolean - if true, the user only needs to have one of the targetPermissions
* to pass the check.
*/
export function hasPermission(
uiPermissions: Array<string>,
targetPermissions: Array<string>,
@ -24,3 +34,27 @@ export function hasPermission(
return !!(!any && isEqual(intersection, targetPermissions));
}
interface usePermissionReturn {
currentUserHasPermission: (targetPermissions: Array<string>) => boolean;
}
/**
* It returns a function that checks if the current user has a permission
*
* @returns An object with a function called currentUserHasPermission
*/
export function usePermission(): usePermissionReturn {
const roleStore = useRoleStore();
const { uiPermissions } = roleStore.permissions;
const currentUserHasPermission = (
targetPermissions: Array<string>
): boolean => {
return hasPermission(uiPermissions, targetPermissions, true);
};
return {
currentUserHasPermission,
};
}