refactor: improve role dependency-related functions and i18n (#5227)

#### What type of PR is this?

/area console
/area core
/milestone 2.12.x

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

优化角色模板依赖的相关功能:

1. 修复勾选某个角色模板之后,其下依赖模板没有选中的问题。
2. 修复编辑角色时,模板其下依赖模板没有选中的问题。
3. 修复角色管理列表中,权限数量显示有误的问题。
4. 移除 **允许管理所有文章** 的角色模板,此角色模板与文章管理重复。
5. 优化 i18n。

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

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

#### Special notes for your reviewer:

需要测试上诉问题是否还存在。

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

```release-note
优化角色模板依赖的相关功能,优化文章相关角色的翻译。
```
pull/5245/head
Ryan Wang 2024-01-24 10:34:11 +08:00 committed by GitHub
parent 57fb644173
commit 28ee0bf0e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 147 additions and 62 deletions

View File

@ -6,7 +6,7 @@ metadata:
halo.run/role-template: "true" halo.run/role-template: "true"
annotations: annotations:
rbac.authorization.halo.run/dependencies: | rbac.authorization.halo.run/dependencies: |
[ "role-template-view-posts", "role-template-manage-snapshots", "role-template-manage-tags", "role-template-manage-categories" ] [ "role-template-view-posts", "role-template-manage-snapshots", "role-template-manage-tags", "role-template-manage-categories", "role-template-post-author" ]
rbac.authorization.halo.run/module: "Posts Management" rbac.authorization.halo.run/module: "Posts Management"
rbac.authorization.halo.run/display-name: "Post Manage" rbac.authorization.halo.run/display-name: "Post Manage"
rbac.authorization.halo.run/ui-permissions: | rbac.authorization.halo.run/ui-permissions: |

View File

@ -8,7 +8,7 @@ metadata:
# Currently, yaml definition does not support i18n, please see https://github.com/halo-dev/halo/issues/3573 # Currently, yaml definition does not support i18n, please see https://github.com/halo-dev/halo/issues/3573
rbac.authorization.halo.run/display-name: "文章管理员" rbac.authorization.halo.run/display-name: "文章管理员"
rbac.authorization.halo.run/dependencies: | rbac.authorization.halo.run/dependencies: |
["role-template-post-editor"] ["role-template-manage-posts"]
rules: [ ] rules: [ ]
--- ---
@ -16,6 +16,8 @@ apiVersion: v1alpha1
kind: "Role" kind: "Role"
metadata: metadata:
name: role-template-post-editor name: role-template-post-editor
# Deprecated, will be removed in the future
deletionTimestamp: 2023-12-01T03:36:25.875373Z
labels: labels:
halo.run/role-template: "true" halo.run/role-template: "true"
annotations: annotations:

View File

@ -11,7 +11,7 @@ import {
VDescriptionItem, VDescriptionItem,
} from "@halo-dev/components"; } from "@halo-dev/components";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { computed, onMounted, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { pluginLabels, roleLabels } from "@/constants/labels"; import { pluginLabels, roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
@ -20,6 +20,8 @@ import { SUPER_ROLE_NAME } from "@/constants/constants";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useQuery } from "@tanstack/vue-query"; import { useQuery } from "@tanstack/vue-query";
import type { Role } from "packages/api-client/dist";
import { resolveDeepDependencies } from "@/utils/role";
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -58,12 +60,14 @@ const getRoleCountText = computed(() => {
return t("core.role.common.text.contains_all_permissions"); return t("core.role.common.text.contains_all_permissions");
} }
const dependenciesCount = JSON.parse( const dependencies = new Set<string>(
formState.value.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] || "[]" resolveDeepDependencies(formState.value, roleTemplates.value || [])
).length; );
console.log(dependencies);
return t("core.role.common.text.contains_n_permissions", { return t("core.role.common.text.contains_n_permissions", {
count: dependenciesCount, count: dependencies.size || 0,
}); });
}); });
@ -77,31 +81,27 @@ watch(
} }
); );
const handleFetchRole = async () => { const { refetch } = useQuery<Role>({
try { queryKey: ["role", route.params.name],
const response = await apiClient.extension.role.getv1alpha1Role({ queryFn: async () => {
const { data } = await apiClient.extension.role.getv1alpha1Role({
name: route.params.name as string, name: route.params.name as string,
}); });
formState.value = response.data; return data;
selectedRoleTemplates.value = new Set( },
JSON.parse( onSuccess(data) {
response.data.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] || formState.value = data;
"[]" selectedRoleTemplates.value = new Set<string>(
) resolveDeepDependencies(data, roleTemplates.value || [])
); );
} catch (error) { },
console.error(error); enabled: computed(() => !!roleTemplates.value),
} });
};
const handleUpdateRole = async () => { const handleUpdateRole = async () => {
await handleCreateOrUpdate(); await handleCreateOrUpdate();
await handleFetchRole(); await refetch();
}; };
onMounted(() => {
handleFetchRole();
});
</script> </script>
<template> <template>
<VPageHeader :title="$t('core.role.detail.title')"> <VPageHeader :title="$t('core.role.detail.title')">

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import { computed, ref, watch } from "vue"; import { computed, ref } from "vue";
import type { Role } from "@halo-dev/api-client"; import type { Role, RoleList } from "@halo-dev/api-client";
// components // components
import { import {
@ -25,9 +25,6 @@ import RoleEditingModal from "./components/RoleEditingModal.vue";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
// hooks
import { useFetchRole } from "@/composables/use-role";
// libs // libs
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
@ -35,6 +32,8 @@ import { usePermission } from "@/utils/permission";
import { roleLabels } from "@/constants/labels"; import { roleLabels } from "@/constants/labels";
import { SUPER_ROLE_NAME } from "@/constants/constants"; import { SUPER_ROLE_NAME } from "@/constants/constants";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useQuery } from "@tanstack/vue-query";
import { resolveDeepDependencies } from "@/utils/role";
const { currentUserHasPermission } = usePermission(); const { currentUserHasPermission } = usePermission();
const { t } = useI18n(); const { t } = useI18n();
@ -42,26 +41,65 @@ const { t } = useI18n();
const editingModal = ref<boolean>(false); const editingModal = ref<boolean>(false);
const selectedRole = ref<Role>(); const selectedRole = ref<Role>();
const { roles, handleFetchRoles, loading } = useFetchRole();
let fuse: Fuse<Role> | undefined = undefined; let fuse: Fuse<Role> | undefined = undefined;
watch( const { data: roleTemplates } = useQuery({
() => roles.value, queryKey: ["role-templates"],
(value) => { queryFn: async () => {
fuse = new Fuse(value, { const { data } = await apiClient.extension.role.listv1alpha1Role({
keys: ["spec.displayName", "metadata.name"], page: 0,
size: 0,
labelSelector: [`${roleLabels.TEMPLATE}=true`, "!halo.run/hidden"],
});
return data.items;
},
});
const {
data: roles,
isLoading,
refetch,
} = useQuery<RoleList>({
queryKey: ["roles"],
queryFn: async () => {
const { data } = await apiClient.extension.role.listv1alpha1Role({
page: 0,
size: 0,
labelSelector: [`!${roleLabels.TEMPLATE}`],
});
return data;
},
refetchInterval(data) {
const hasDeletingRole = data?.items.some(
(item) => !!item.metadata.deletionTimestamp
);
return hasDeletingRole ? 1000 : false;
},
onSuccess(data) {
fuse = new Fuse(data.items, {
keys: [
{
name: "displayName",
getFn: (role) => {
return (
role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] || ""
);
},
},
"metadata.name",
],
useExtendedSearch: true, useExtendedSearch: true,
threshold: 0.2, threshold: 0.2,
}); });
} },
); enabled: computed(() => !!roleTemplates.value),
});
const keyword = ref(""); const keyword = ref("");
const searchResults = computed(() => { const searchResults = computed(() => {
if (!fuse || !keyword.value) { if (!fuse || !keyword.value) {
return roles.value; return roles.value?.items || [];
} }
return fuse?.search(keyword.value).map((item) => item.item); return fuse?.search(keyword.value).map((item) => item.item);
@ -76,12 +114,12 @@ const getRoleCountText = (role: Role) => {
return t("core.role.common.text.contains_all_permissions"); return t("core.role.common.text.contains_all_permissions");
} }
const dependenciesCount = JSON.parse( const dependencies = new Set<string>(
role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] || "[]" resolveDeepDependencies(role, roleTemplates.value || [])
).length; );
return t("core.role.common.text.contains_n_permissions", { return t("core.role.common.text.contains_n_permissions", {
count: dependenciesCount, count: dependencies.size || 0,
}); });
}; };
@ -92,7 +130,7 @@ const handleOpenEditingModal = (role: Role) => {
const onEditingModalClose = () => { const onEditingModalClose = () => {
selectedRole.value = undefined; selectedRole.value = undefined;
handleFetchRoles(); refetch();
}; };
const handleCloneRole = async (role: Role) => { const handleCloneRole = async (role: Role) => {
@ -152,7 +190,7 @@ const handleDelete = async (role: Role) => {
} catch (e) { } catch (e) {
console.error("Failed to delete role", e); console.error("Failed to delete role", e);
} finally { } finally {
handleFetchRoles(); refetch();
} }
}, },
}); });
@ -200,7 +238,7 @@ const handleDelete = async (role: Role) => {
</div> </div>
</div> </div>
</template> </template>
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else appear name="fade"> <Transition v-else appear name="fade">
<ul <ul
class="box-border h-full w-full divide-y divide-gray-100" class="box-border h-full w-full divide-y divide-gray-100"

View File

@ -6,6 +6,7 @@ import { rbacAnnotations } from "@/constants/annotations";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { Toast } from "@halo-dev/components"; import { Toast } from "@halo-dev/components";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { resolveDeepDependencies } from "@/utils/role";
interface RoleTemplateGroup { interface RoleTemplateGroup {
module: string | null | undefined; module: string | null | undefined;
@ -38,7 +39,7 @@ interface useRoleFormReturn {
initialFormState: Role; initialFormState: Role;
saving: Ref<boolean>; saving: Ref<boolean>;
isUpdateMode: ComputedRef<boolean>; isUpdateMode: ComputedRef<boolean>;
handleCreateOrUpdate: () => void; handleCreateOrUpdate: () => Promise<void>;
} }
interface useRoleTemplateSelectionReturn { interface useRoleTemplateSelectionReturn {
@ -271,15 +272,15 @@ export function useRoleTemplateSelection(
const role = roleTemplates.value?.find( const role = roleTemplates.value?.find(
(role) => role.metadata.name === value (role) => role.metadata.name === value
); );
const dependencies =
role?.metadata.annotations?.[rbacAnnotations.DEPENDENCIES]; if (!role) {
if (!dependencies) {
return; return;
} }
const dependenciesArray = JSON.parse(dependencies);
dependenciesArray.forEach((role) => { selectedRoleTemplates.value = new Set([
selectedRoleTemplates.value.add(role); role.metadata.name,
}); ...resolveDeepDependencies(role, roleTemplates.value || []),
]);
}; };
return { return {

View File

@ -1322,6 +1322,11 @@ core:
role-template-view-tags: Tag View role-template-view-tags: Tag View
role-template-manage-categories: Category Manage role-template-manage-categories: Category Manage
role-template-view-categories: Category View role-template-view-categories: Category View
role-template-manage-posts: Post Manage
role-template-post-author: Allows you to manage your own posts
role-template-post-contributor: Contributions allowed
role-template-post-publisher: Allow to publish own posts
role-template-post-attachment-manager: Allow images to be uploaded in posts
Roles Management: Roles Roles Management: Roles
Role Manage: Role Manage Role Manage: Role Manage
Role View: Role View Role View: Role View
@ -1352,9 +1357,8 @@ core:
Notification Configuration: Notification Configuration Notification Configuration: Notification Configuration
Configure Notifier: Configure Notifier Configure Notifier: Configure Notifier
Post Attachment Manager: Allow images to be uploaded in posts Post Attachment Manager: Allow images to be uploaded in posts
Post Author: Contributions allowed Post Author: Allows you to manage your own posts
Post Contributor: Allows you to manage your own posts Post Contributor: Contributions allowed
Post Editor: Allow management of all posts
Post Publisher: Allow to publish own posts Post Publisher: Allow to publish own posts
components: components:
submit_button: submit_button:

View File

@ -1270,6 +1270,11 @@ core:
role-template-view-tags: 标签查看 role-template-view-tags: 标签查看
role-template-manage-categories: 分类管理 role-template-manage-categories: 分类管理
role-template-view-categories: 分类查看 role-template-view-categories: 分类查看
role-template-manage-posts: 文章管理
role-template-post-author: 允许管理自己的文章
role-template-post-contributor: 允许投稿
role-template-post-publisher: 允许发布自己的文章
role-template-post-attachment-manager: 允许在文章中上传图片
Roles Management: 角色 Roles Management: 角色
Role Manage: 角色管理 Role Manage: 角色管理
Role View: 角色查看 Role View: 角色查看
@ -1299,9 +1304,8 @@ core:
Cache Manage: 缓存管理 Cache Manage: 缓存管理
Notification Configuration: 通知配置 Notification Configuration: 通知配置
Configure Notifier: 配置通知器 Configure Notifier: 配置通知器
Post Editor: 允许管理所有文章 Post Contributor: 允许投稿
Post Contributor: 允许管理自己的文章 Post Author: 允许管理自己的文章
Post Author: 允许投稿
Post Attachment Manager: 允许在文章中上传图片 Post Attachment Manager: 允许在文章中上传图片
Post Publisher: 允许发布自己的文章 Post Publisher: 允许发布自己的文章
components: components:

View File

@ -1236,6 +1236,11 @@ core:
role-template-view-tags: 標籤查看 role-template-view-tags: 標籤查看
role-template-manage-categories: 分類管理 role-template-manage-categories: 分類管理
role-template-view-categories: 分類查看 role-template-view-categories: 分類查看
role-template-manage-posts: 文章管理
role-template-post-author: 允许管理自己的文章
role-template-post-contributor: 允许投稿
role-template-post-publisher: 允許發布自己的文章
role-template-post-attachment-manager: 允許在文章中上傳圖片
Roles Management: 角色 Roles Management: 角色
Role Manage: 角色管理 Role Manage: 角色管理
Role View: 角色查看 Role View: 角色查看
@ -1268,7 +1273,6 @@ core:
Post Attachment Manager: 允許在文章中上傳圖片 Post Attachment Manager: 允許在文章中上傳圖片
Post Author: 允许管理自己的文章 Post Author: 允许管理自己的文章
Post Contributor: 允许投稿 Post Contributor: 允许投稿
Post Editor: 允许管理所有文章
Post Publisher: 允許發布自己的文章 Post Publisher: 允許發布自己的文章
components: components:
submit_button: submit_button:

32
console/src/utils/role.ts Normal file
View File

@ -0,0 +1,32 @@
import { rbacAnnotations } from "@/constants/annotations";
import type { Role } from "@halo-dev/api-client";
export function resolveDeepDependencies(
role: Role,
roleTemplates: Role[]
): string[] {
if (!role) {
return [];
}
const result: string[] = [];
const dependencies: string[] = JSON.parse(
role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] || "[]"
);
dependencies.forEach((depName) => {
result.push(depName);
const dep = roleTemplates.find((item) => item.metadata.name === depName);
if (!dep) {
return;
}
resolveDeepDependencies(dep, roleTemplates).forEach((nextDep) =>
result.push(nextDep)
);
});
return result;
}