halo/ui/console-src/modules/system/roles/RoleDetail.vue

301 lines
9.9 KiB
Vue
Raw Normal View History

<script lang="ts" setup>
import {
IconShieldUser,
VButton,
VCard,
VPageHeader,
VTabbar,
VTag,
VAlert,
VDescription,
VDescriptionItem,
} from "@halo-dev/components";
import { useRoute } from "vue-router";
import { computed, ref, watch } from "vue";
import { apiClient } from "@/utils/api-client";
import { pluginLabels, roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations";
import { useRoleForm, useRoleTemplateSelection } from "@/composables/use-role";
import { SUPER_ROLE_NAME } from "@/constants/constants";
import { useI18n } from "vue-i18n";
import { formatDatetime } from "@/utils/date";
import { useQuery } from "@tanstack/vue-query";
import type { Role } from "packages/api-client/dist";
import { resolveDeepDependencies } from "@/utils/role";
const route = useRoute();
const { t } = useI18n();
const tabActiveId = ref("detail");
const { data: roleTemplates } = useQuery({
queryKey: ["role-templates"],
queryFn: async () => {
const { data } = await apiClient.extension.role.listV1alpha1Role({
page: 0,
size: 0,
labelSelector: [`${roleLabels.TEMPLATE}=true`, "!halo.run/hidden"],
});
return data.items;
},
});
const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } =
useRoleTemplateSelection(roleTemplates);
const { formState, saving, handleCreateOrUpdate } = useRoleForm();
const isSystemReserved = computed(() => {
return (
formState.value.metadata.labels?.[roleLabels.SYSTEM_RESERVED] === "true"
);
});
const isSuperRole = computed(() => {
return formState.value.metadata.name === SUPER_ROLE_NAME;
});
const getRoleCountText = computed(() => {
if (formState.value.metadata.name === SUPER_ROLE_NAME) {
return t("core.role.common.text.contains_all_permissions");
}
const dependencies = new Set<string>(
resolveDeepDependencies(formState.value, roleTemplates.value || [])
);
console.log(dependencies);
return t("core.role.common.text.contains_n_permissions", {
count: dependencies.size || 0,
});
});
watch(
() => selectedRoleTemplates.value,
(newValue) => {
if (formState.value.metadata.annotations) {
formState.value.metadata.annotations[rbacAnnotations.DEPENDENCIES] =
JSON.stringify(Array.from(newValue));
}
}
);
const { refetch } = useQuery<Role>({
queryKey: ["role", route.params.name],
queryFn: async () => {
const { data } = await apiClient.extension.role.getV1alpha1Role({
refactor: method parameters of api client (halo-dev/console#605) <!-- Thanks for sending a pull request! Here are some tips for you: 1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。 1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>. 2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。 2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. 3. 请确保你已经添加并运行了适当的测试。 3. Ensure you have added or ran the appropriate tests for your PR. --> #### What type of PR is this? /kind improvement /milestone 2.0 <!-- 添加其中一个类别: Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature /kind optimization 适当添加其中一个或多个类别(可选): Optionally add one or more of the following kinds if applicable: /kind api-change /kind deprecation /kind failing-test /kind flake /kind regression --> #### What this PR does / why we need it: 修改 api-client 的请求参数结构,改为所有参数由一个对象包裹,而不是将各个参数作为方法的参数,防止因为后端参数结构发生改变,或者生成 api-client 时参数顺序发生改变导致请求异常。如: ```diff await apiClient.extension.storage.group.updatestorageHaloRunV1alpha1Group( - formState.value.metadata.name, - formState.value + { + name: formState.value.metadata.name, + group: formState.value, + } ); ``` #### Which issue(s) this PR fixes: <!-- PR 合并时自动关闭 issue。 Automatically closes linked issue when PR is merged. 用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)` Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> None #### Screenshots: <!-- 如果此 PR 有 UI 的改动,最好截图说明这个 PR 的改动。 If there are UI changes to this PR, it is best to take a screenshot to illustrate the changes to this PR. eg. Before: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> None #### Special notes for your reviewer: /cc @halo-dev/sig-halo-admin #### Does this PR introduce a user-facing change? <!-- 如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。 否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change), Release Note 需要以 `action required` 开头。 If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". --> ```release-note None ```
2022-09-06 02:26:11 +00:00
name: route.params.name as string,
});
return data;
},
onSuccess(data) {
formState.value = data;
selectedRoleTemplates.value = new Set<string>(
resolveDeepDependencies(data, roleTemplates.value || [])
);
},
enabled: computed(() => !!roleTemplates.value),
});
const handleUpdateRole = async () => {
await handleCreateOrUpdate();
await refetch();
};
</script>
<template>
<VPageHeader :title="$t('core.role.detail.title')">
<template #icon>
<IconShieldUser class="mr-2 self-center" />
</template>
</VPageHeader>
<div class="m-0 md:m-4">
<VCard :body-class="['!p-0']">
<template #header>
<VTabbar
v-model:active-id="tabActiveId"
:items="[
{ id: 'detail', label: $t('core.role.detail.tabs.detail') },
{
id: 'permissions',
label: $t('core.role.detail.tabs.permissions'),
},
]"
class="w-full !rounded-none"
type="outline"
></VTabbar>
</template>
<div v-if="tabActiveId === 'detail'">
<div class="px-4 py-4 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ $t("core.role.detail.header.title") }}
</h3>
<p
class="mt-1 flex max-w-2xl items-center gap-2 text-sm text-gray-500"
>
<span>{{ getRoleCountText }}</span>
</p>
</div>
<div class="border-t border-gray-200">
<VDescription>
<VDescriptionItem
:label="$t('core.role.detail.fields.display_name')"
>
{{
formState.metadata?.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || formState.metadata?.name
}}
</VDescriptionItem>
<VDescriptionItem
:label="$t('core.role.detail.fields.name')"
:content="formState.metadata?.name"
/>
<VDescriptionItem :label="$t('core.role.detail.fields.type')">
<VTag>
{{
isSystemReserved
? t("core.role.common.text.system_reserved")
: t("core.role.common.text.custom")
}}
</VTag>
</VDescriptionItem>
<VDescriptionItem
:label="$t('core.role.detail.fields.creation_time')"
:content="formatDatetime(formState.metadata.creationTimestamp)"
/>
</VDescription>
</div>
</div>
<div v-if="tabActiveId === 'permissions'">
<div v-if="isSystemReserved" class="px-4 py-5">
<VAlert
:title="$t('core.common.text.tip')"
:description="
$t(
'core.role.permissions_detail.system_reserved_alert.description'
)
"
class="w-full sm:w-1/4"
:closable="false"
/>
</div>
<div>
<dl class="divide-y divide-gray-100">
<div
v-for="(group, groupIndex) in roleTemplateGroups"
:key="groupIndex"
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">
<div>
{{ $t(`core.rbac.${group.module}`, group.module as string) }}
</div>
<div
v-if="
group.roles.length &&
group.roles[0].metadata.labels?.[pluginLabels.NAME]
"
class="mt-3 text-xs text-gray-500"
>
<i18n-t
keypath="core.role.common.text.provided_by_plugin"
tag="div"
>
<template #plugin>
<RouterLink
:to="{
name: 'PluginDetail',
params: {
name: group.roles[0].metadata.labels?.[
pluginLabels.NAME
],
},
}"
class="hover:text-blue-600"
>
{{
group.roles[0].metadata.labels?.[pluginLabels.NAME]
}}
</RouterLink>
</template>
</i18n-t>
</div>
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<ul class="space-y-2">
<li v-for="(role, index) in group.roles" :key="index">
<label
class="inline-flex w-72 cursor-pointer flex-row items-center gap-4 rounded-base border p-5 hover:border-primary"
>
<input
v-if="!isSuperRole"
v-model="selectedRoleTemplates"
:value="role.metadata.name"
type="checkbox"
:disabled="isSystemReserved"
@change="handleRoleTemplateSelect"
/>
<input v-else type="checkbox" checked disabled />
<div class="flex flex-1 flex-col gap-y-3">
<span class="font-medium text-gray-900">
{{
$t(
`core.rbac.${
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
]
}`,
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] as string
)
}}
</span>
<span
v-if="
role.metadata.annotations?.[
rbacAnnotations.DEPENDENCIES
]
"
class="text-xs text-gray-400"
>
{{
$t("core.role.common.text.dependent_on", {
roles: JSON.parse(
role.metadata.annotations?.[
rbacAnnotations.DEPENDENCIES
]
)
.map((item: string) =>
$t(`core.rbac.${item}`, item as string)
)
.join(""),
})
}}
</span>
</div>
</label>
</li>
</ul>
</dd>
</div>
</dl>
<div v-permission="['system:roles:manage']" class="p-4">
<VButton
:loading="saving"
type="secondary"
:disabled="isSystemReserved"
@click="handleUpdateRole"
>
{{ $t("core.common.buttons.save") }}
</VButton>
</div>
</div>
</div>
</VCard>
</div>
</template>