mirror of https://github.com/halo-dev/halo
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
parent
57fb644173
commit
28ee0bf0e0
|
@ -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: |
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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')">
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue