refactor: request api of user data (#882)

#### What type of PR is this?

/kind improvement

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

重构获取用户信息的请求方式,无需再请求所有角色即可获取当前用户的角色信息,适配:https://github.com/halo-dev/halo/pull/3372

相关 PR:https://github.com/halo-dev/console/pull/847

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

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

#### Special notes for your reviewer:

测试方式:

1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/3372 分支。
2. Console 需要 `pnpm install && pnpm build:packages`
3. 测试用户列表、登录、检查角色信息是否显示正确。

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

```release-note
优化 Console 端用户角色标识的显示名称。
```
pull/883/head^2
Ryan Wang 2023-02-24 14:06:14 +08:00 committed by GitHub
parent 66a626c916
commit 6244e8b5c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 370 additions and 152 deletions

View File

@ -77,6 +77,7 @@ models/counter-request.ts
models/counter.ts models/counter.ts
models/custom-templates.ts models/custom-templates.ts
models/dashboard-stats.ts models/dashboard-stats.ts
models/detailed-user.ts
models/excerpt.ts models/excerpt.ts
models/extension.ts models/extension.ts
models/file-reverse-proxy-provider.ts models/file-reverse-proxy-provider.ts
@ -96,6 +97,8 @@ models/listed-reply-list.ts
models/listed-reply.ts models/listed-reply.ts
models/listed-single-page-list.ts models/listed-single-page-list.ts
models/listed-single-page.ts models/listed-single-page.ts
models/listed-user-list.ts
models/listed-user.ts
models/login-history.ts models/login-history.ts
models/menu-item-list.ts models/menu-item-list.ts
models/menu-item-spec.ts models/menu-item-spec.ts

View File

@ -34,11 +34,13 @@ import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } fr
// @ts-ignore // @ts-ignore
import { ChangePasswordRequest } from '../models' import { ChangePasswordRequest } from '../models'
// @ts-ignore // @ts-ignore
import { DetailedUser } from '../models'
// @ts-ignore
import { GrantRequest } from '../models' import { GrantRequest } from '../models'
// @ts-ignore // @ts-ignore
import { User } from '../models' import { ListedUserList } from '../models'
// @ts-ignore // @ts-ignore
import { UserList } from '../models' import { User } from '../models'
// @ts-ignore // @ts-ignore
import { UserPermission } from '../models' import { UserPermission } from '../models'
/** /**
@ -174,6 +176,47 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
options: localVarRequestOptions, options: localVarRequestOptions,
} }
}, },
/**
* Get user detail by name
* @param {string} name User name
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUserDetail: async (name: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'name' is not null or undefined
assertParamExists('getUserDetail', 'name', name)
const localVarPath = `/apis/api.console.halo.run/v1alpha1/users/{name}`.replace(
`{${'name'}}`,
encodeURIComponent(String(name)),
)
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options }
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
// authentication BasicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers }
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/** /**
* Grant permissions to user * Grant permissions to user
* @param {string} name User name * @param {string} name User name
@ -231,9 +274,9 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
* @param {string} [keyword] * @param {string} [keyword]
* @param {string} [role] * @param {string} [role]
* @param {number} [size] Size of one page. Zero indicates no limit. * @param {number} [size] Size of one page. Zero indicates no limit.
* @param {number} [page] The page number. Zero indicates no page.
* @param {Array<string>} [labelSelector] Label selector for filtering. * @param {Array<string>} [labelSelector] Label selector for filtering.
* @param {Array<string>} [fieldSelector] Field selector for filtering. * @param {Array<string>} [fieldSelector] Field selector for filtering.
* @param {number} [page] The page number. Zero indicates no page.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
@ -242,9 +285,9 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
keyword?: string, keyword?: string,
role?: string, role?: string,
size?: number, size?: number,
page?: number,
labelSelector?: Array<string>, labelSelector?: Array<string>,
fieldSelector?: Array<string>, fieldSelector?: Array<string>,
page?: number,
options: AxiosRequestConfig = {}, options: AxiosRequestConfig = {},
): Promise<RequestArgs> => { ): Promise<RequestArgs> => {
const localVarPath = `/apis/api.console.halo.run/v1alpha1/users` const localVarPath = `/apis/api.console.halo.run/v1alpha1/users`
@ -283,10 +326,6 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
localVarQueryParameter['size'] = size localVarQueryParameter['size'] = size
} }
if (page !== undefined) {
localVarQueryParameter['page'] = page
}
if (labelSelector) { if (labelSelector) {
localVarQueryParameter['labelSelector'] = labelSelector localVarQueryParameter['labelSelector'] = labelSelector
} }
@ -295,6 +334,10 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
localVarQueryParameter['fieldSelector'] = fieldSelector localVarQueryParameter['fieldSelector'] = fieldSelector
} }
if (page !== undefined) {
localVarQueryParameter['page'] = page
}
setSearchParams(localVarUrlObj, localVarQueryParameter) setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {} let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers } localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers }
@ -377,7 +420,7 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function (configuration?: Conf
*/ */
async getCurrentUserDetail( async getCurrentUserDetail(
options?: AxiosRequestConfig, options?: AxiosRequestConfig,
): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<User>> { ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DetailedUser>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getCurrentUserDetail(options) const localVarAxiosArgs = await localVarAxiosParamCreator.getCurrentUserDetail(options)
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration) return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
}, },
@ -394,6 +437,19 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function (configuration?: Conf
const localVarAxiosArgs = await localVarAxiosParamCreator.getPermissions(name, options) const localVarAxiosArgs = await localVarAxiosParamCreator.getPermissions(name, options)
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration) return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
}, },
/**
* Get user detail by name
* @param {string} name User name
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getUserDetail(
name: string,
options?: AxiosRequestConfig,
): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DetailedUser>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getUserDetail(name, options)
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
},
/** /**
* Grant permissions to user * Grant permissions to user
* @param {string} name User name * @param {string} name User name
@ -415,9 +471,9 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function (configuration?: Conf
* @param {string} [keyword] * @param {string} [keyword]
* @param {string} [role] * @param {string} [role]
* @param {number} [size] Size of one page. Zero indicates no limit. * @param {number} [size] Size of one page. Zero indicates no limit.
* @param {number} [page] The page number. Zero indicates no page.
* @param {Array<string>} [labelSelector] Label selector for filtering. * @param {Array<string>} [labelSelector] Label selector for filtering.
* @param {Array<string>} [fieldSelector] Field selector for filtering. * @param {Array<string>} [fieldSelector] Field selector for filtering.
* @param {number} [page] The page number. Zero indicates no page.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
@ -426,19 +482,19 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function (configuration?: Conf
keyword?: string, keyword?: string,
role?: string, role?: string,
size?: number, size?: number,
page?: number,
labelSelector?: Array<string>, labelSelector?: Array<string>,
fieldSelector?: Array<string>, fieldSelector?: Array<string>,
page?: number,
options?: AxiosRequestConfig, options?: AxiosRequestConfig,
): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserList>> { ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedUserList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers( const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers(
sort, sort,
keyword, keyword,
role, role,
size, size,
page,
labelSelector, labelSelector,
fieldSelector, fieldSelector,
page,
options, options,
) )
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration) return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
@ -489,7 +545,7 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getCurrentUserDetail(options?: AxiosRequestConfig): AxiosPromise<User> { getCurrentUserDetail(options?: AxiosRequestConfig): AxiosPromise<DetailedUser> {
return localVarFp.getCurrentUserDetail(options).then((request) => request(axios, basePath)) return localVarFp.getCurrentUserDetail(options).then((request) => request(axios, basePath))
}, },
/** /**
@ -504,6 +560,18 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (
): AxiosPromise<UserPermission> { ): AxiosPromise<UserPermission> {
return localVarFp.getPermissions(requestParameters.name, options).then((request) => request(axios, basePath)) return localVarFp.getPermissions(requestParameters.name, options).then((request) => request(axios, basePath))
}, },
/**
* Get user detail by name
* @param {ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUserDetail(
requestParameters: ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest,
options?: AxiosRequestConfig,
): AxiosPromise<DetailedUser> {
return localVarFp.getUserDetail(requestParameters.name, options).then((request) => request(axios, basePath))
},
/** /**
* Grant permissions to user * Grant permissions to user
* @param {ApiConsoleHaloRunV1alpha1UserApiGrantPermissionRequest} requestParameters Request parameters. * @param {ApiConsoleHaloRunV1alpha1UserApiGrantPermissionRequest} requestParameters Request parameters.
@ -527,16 +595,16 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (
listUsers( listUsers(
requestParameters: ApiConsoleHaloRunV1alpha1UserApiListUsersRequest = {}, requestParameters: ApiConsoleHaloRunV1alpha1UserApiListUsersRequest = {},
options?: AxiosRequestConfig, options?: AxiosRequestConfig,
): AxiosPromise<UserList> { ): AxiosPromise<ListedUserList> {
return localVarFp return localVarFp
.listUsers( .listUsers(
requestParameters.sort, requestParameters.sort,
requestParameters.keyword, requestParameters.keyword,
requestParameters.role, requestParameters.role,
requestParameters.size, requestParameters.size,
requestParameters.page,
requestParameters.labelSelector, requestParameters.labelSelector,
requestParameters.fieldSelector, requestParameters.fieldSelector,
requestParameters.page,
options, options,
) )
.then((request) => request(axios, basePath)) .then((request) => request(axios, basePath))
@ -591,6 +659,20 @@ export interface ApiConsoleHaloRunV1alpha1UserApiGetPermissionsRequest {
readonly name: string readonly name: string
} }
/**
* Request parameters for getUserDetail operation in ApiConsoleHaloRunV1alpha1UserApi.
* @export
* @interface ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest
*/
export interface ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest {
/**
* User name
* @type {string}
* @memberof ApiConsoleHaloRunV1alpha1UserApiGetUserDetail
*/
readonly name: string
}
/** /**
* Request parameters for grantPermission operation in ApiConsoleHaloRunV1alpha1UserApi. * Request parameters for grantPermission operation in ApiConsoleHaloRunV1alpha1UserApi.
* @export * @export
@ -646,13 +728,6 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest {
*/ */
readonly size?: number readonly size?: number
/**
* The page number. Zero indicates no page.
* @type {number}
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
*/
readonly page?: number
/** /**
* Label selector for filtering. * Label selector for filtering.
* @type {Array<string>} * @type {Array<string>}
@ -666,6 +741,13 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest {
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
*/ */
readonly fieldSelector?: Array<string> readonly fieldSelector?: Array<string>
/**
* The page number. Zero indicates no page.
* @type {number}
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
*/
readonly page?: number
} }
/** /**
@ -733,6 +815,22 @@ export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath)) .then((request) => request(this.axios, this.basePath))
} }
/**
* Get user detail by name
* @param {ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ApiConsoleHaloRunV1alpha1UserApi
*/
public getUserDetail(
requestParameters: ApiConsoleHaloRunV1alpha1UserApiGetUserDetailRequest,
options?: AxiosRequestConfig,
) {
return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration)
.getUserDetail(requestParameters.name, options)
.then((request) => request(this.axios, this.basePath))
}
/** /**
* Grant permissions to user * Grant permissions to user
* @param {ApiConsoleHaloRunV1alpha1UserApiGrantPermissionRequest} requestParameters Request parameters. * @param {ApiConsoleHaloRunV1alpha1UserApiGrantPermissionRequest} requestParameters Request parameters.
@ -766,9 +864,9 @@ export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI {
requestParameters.keyword, requestParameters.keyword,
requestParameters.role, requestParameters.role,
requestParameters.size, requestParameters.size,
requestParameters.page,
requestParameters.labelSelector, requestParameters.labelSelector,
requestParameters.fieldSelector, requestParameters.fieldSelector,
requestParameters.page,
options, options,
) )
.then((request) => request(this.axios, this.basePath)) .then((request) => request(this.axios, this.basePath))

View File

@ -0,0 +1,40 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo Next API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import { Role } from './role'
// May contain unused imports in some cases
// @ts-ignore
import { User } from './user'
/**
*
* @export
* @interface DetailedUser
*/
export interface DetailedUser {
/**
*
* @type {User}
* @memberof DetailedUser
*/
user: User
/**
*
* @type {Array<Role>}
* @memberof DetailedUser
*/
roles: Array<Role>
}

View File

@ -31,6 +31,7 @@ export * from './counter-list'
export * from './counter-request' export * from './counter-request'
export * from './custom-templates' export * from './custom-templates'
export * from './dashboard-stats' export * from './dashboard-stats'
export * from './detailed-user'
export * from './excerpt' export * from './excerpt'
export * from './extension' export * from './extension'
export * from './file-reverse-proxy-provider' export * from './file-reverse-proxy-provider'
@ -49,6 +50,8 @@ export * from './listed-reply'
export * from './listed-reply-list' export * from './listed-reply-list'
export * from './listed-single-page' export * from './listed-single-page'
export * from './listed-single-page-list' export * from './listed-single-page-list'
export * from './listed-user'
export * from './listed-user-list'
export * from './login-history' export * from './login-history'
export * from './menu' export * from './menu'
export * from './menu-item' export * from './menu-item'

View File

@ -0,0 +1,79 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo Next API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import { ListedUser } from './listed-user'
/**
*
* @export
* @interface ListedUserList
*/
export interface ListedUserList {
/**
* Page number, starts from 1. If not set or equal to 0, it means no pagination.
* @type {number}
* @memberof ListedUserList
*/
page: number
/**
* Size of each page. If not set or equal to 0, it means no pagination.
* @type {number}
* @memberof ListedUserList
*/
size: number
/**
* Total elements.
* @type {number}
* @memberof ListedUserList
*/
total: number
/**
* A chunk of items.
* @type {Array<ListedUser>}
* @memberof ListedUserList
*/
items: Array<ListedUser>
/**
* Indicates whether current page is the first page.
* @type {boolean}
* @memberof ListedUserList
*/
first: boolean
/**
* Indicates whether current page is the last page.
* @type {boolean}
* @memberof ListedUserList
*/
last: boolean
/**
* Indicates whether current page has previous page.
* @type {boolean}
* @memberof ListedUserList
*/
hasNext: boolean
/**
* Indicates whether current page has previous page.
* @type {boolean}
* @memberof ListedUserList
*/
hasPrevious: boolean
/**
* Indicates total pages.
* @type {number}
* @memberof ListedUserList
*/
totalPages: number
}

View File

@ -0,0 +1,40 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo Next API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import { Role } from './role'
// May contain unused imports in some cases
// @ts-ignore
import { User } from './user'
/**
* A chunk of items.
* @export
* @interface ListedUser
*/
export interface ListedUser {
/**
*
* @type {User}
* @memberof ListedUser
*/
user: User
/**
*
* @type {Array<Role>}
* @memberof ListedUser
*/
roles: Array<Role>
}

View File

@ -18,7 +18,7 @@ import {
useRouter, useRouter,
type RouteRecordRaw, type RouteRecordRaw,
} from "vue-router"; } from "vue-router";
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import axios from "axios"; import axios from "axios";
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue"; import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
import LoginModal from "@/components/login/LoginModal.vue"; import LoginModal from "@/components/login/LoginModal.vue";
@ -29,7 +29,7 @@ import { hasPermission } from "@/utils/permission";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { useScroll } from "@vueuse/core"; import { useScroll } from "@vueuse/core";
import { defineStore } from "pinia"; import { defineStore, storeToRefs } from "pinia";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -39,6 +39,8 @@ const moreMenuRootVisible = ref(false);
const userStore = useUserStore(); const userStore = useUserStore();
const { currentRoles, currentUser } = storeToRefs(userStore);
const handleLogout = () => { const handleLogout = () => {
Dialog.warning({ Dialog.warning({
title: "确定要退出登录吗?", title: "确定要退出登录吗?",
@ -58,19 +60,6 @@ const handleLogout = () => {
}); });
}; };
const currentRole = computed(() => {
const names = JSON.parse(
userStore.currentUser?.metadata.annotations?.[rbacAnnotations.ROLE_NAMES] ||
"[]"
);
if (names.length === 0) {
return;
}
return roleStore.getRoleDisplayName(names[0]);
});
// Global Search // Global Search
const globalSearchVisible = ref(false); const globalSearchVisible = ref(false);
@ -249,24 +238,28 @@ onMounted(() => {
</div> </div>
<div class="profile-placeholder"> <div class="profile-placeholder">
<div class="current-profile"> <div class="current-profile">
<div v-if="userStore.currentUser?.spec.avatar" class="profile-avatar"> <div v-if="currentUser?.spec.avatar" class="profile-avatar">
<VAvatar <VAvatar
:src="userStore.currentUser?.spec.avatar" :src="currentUser?.spec.avatar"
:alt="userStore.currentUser?.spec.displayName" :alt="currentUser?.spec.displayName"
size="md" size="md"
circle circle
></VAvatar> ></VAvatar>
</div> </div>
<div class="profile-name"> <div class="profile-name">
<div class="flex text-sm font-medium"> <div class="flex text-sm font-medium">
{{ userStore.currentUser?.spec.displayName }} {{ currentUser?.spec.displayName }}
</div> </div>
<div v-if="currentRole" class="flex"> <div v-if="currentRoles?.[0]" class="flex">
<VTag> <VTag>
<template #leftIcon> <template #leftIcon>
<IconUserSettings /> <IconUserSettings />
</template> </template>
{{ currentRole }} {{
currentRoles[0].metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || currentRoles[0].metadata.name
}}
</VTag> </VTag>
</div> </div>
</div> </div>

View File

@ -244,9 +244,6 @@ async function initApp() {
await loadUserPermissions(); await loadUserPermissions();
const roleStore = useRoleStore();
await roleStore.fetchRoles();
try { try {
await loadPluginModules(); await loadPluginModules();
} catch (e) { } catch (e) {

View File

@ -36,9 +36,7 @@ import Fuse from "fuse.js";
import { usePermission } from "@/utils/permission"; 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 { useRoleStore } from "@/stores/role";
const roleStore = useRoleStore();
const { currentUserHasPermission } = usePermission(); const { currentUserHasPermission } = usePermission();
const editingModal = ref<boolean>(false); const editingModal = ref<boolean>(false);
@ -93,7 +91,6 @@ const handleOpenEditingModal = (role: Role) => {
const onEditingModalClose = () => { const onEditingModalClose = () => {
selectedRole.value = undefined; selectedRole.value = undefined;
handleFetchRoles(); handleFetchRoles();
roleStore.fetchRoles();
}; };
const handleCloneRole = async (role: Role) => { const handleCloneRole = async (role: Role) => {
@ -143,7 +140,6 @@ const handleDelete = async (role: Role) => {
console.error("Failed to delete role", e); console.error("Failed to delete role", e);
} finally { } finally {
handleFetchRoles(); handleFetchRoles();
roleStore.fetchRoles();
} }
}, },
}); });

View File

@ -1,26 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { IconUserSettings, VTag } from "@halo-dev/components"; import { IconUserSettings, VTag } from "@halo-dev/components";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { computed, inject } from "vue"; import { inject } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import type { User } from "@halo-dev/api-client"; import type { DetailedUser } from "@halo-dev/api-client";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useRoleStore } from "@/stores/role";
const user = inject<Ref<User>>("user"); const user = inject<Ref<DetailedUser | undefined>>("user");
const roleStore = useRoleStore();
const roles = computed(() => {
const names = JSON.parse(
user?.value?.metadata?.annotations?.[rbacAnnotations.ROLE_NAMES] || "[]"
);
return names.map((name: string) => {
return roleStore.getRoleDisplayName(name);
});
});
const router = useRouter(); const router = useRouter();
</script> </script>
@ -32,7 +19,7 @@ const router = useRouter();
> >
<dt class="text-sm font-medium text-gray-900">显示名称</dt> <dt class="text-sm font-medium text-gray-900">显示名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ user?.spec?.displayName }} {{ user?.user.spec?.displayName }}
</dd> </dd>
</div> </div>
<div <div
@ -40,7 +27,7 @@ const router = useRouter();
> >
<dt class="text-sm font-medium text-gray-900">用户名</dt> <dt class="text-sm font-medium text-gray-900">用户名</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ user?.metadata?.name }} {{ user?.user.metadata?.name }}
</dd> </dd>
</div> </div>
<div <div
@ -48,7 +35,7 @@ const router = useRouter();
> >
<dt class="text-sm font-medium text-gray-900">电子邮箱</dt> <dt class="text-sm font-medium text-gray-900">电子邮箱</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ user?.spec?.email || "未设置" }} {{ user?.user.spec?.email || "未设置" }}
</dd> </dd>
</div> </div>
<div <div
@ -57,14 +44,22 @@ const router = useRouter();
<dt class="text-sm font-medium text-gray-900">角色</dt> <dt class="text-sm font-medium text-gray-900">角色</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
<VTag <VTag
v-for="(role, index) in roles" v-for="(role, index) in user?.roles"
:key="index" :key="index"
@click="router.push({ name: 'RoleDetail', params: { name: role } })" @click="
router.push({
name: 'RoleDetail',
params: { name: role.metadata.name },
})
"
> >
<template #leftIcon> <template #leftIcon>
<IconUserSettings /> <IconUserSettings />
</template> </template>
{{ role }} {{
role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
role.metadata.name
}}
</VTag> </VTag>
</dd> </dd>
</div> </div>
@ -73,7 +68,7 @@ const router = useRouter();
> >
<dt class="text-sm font-medium text-gray-900">描述</dt> <dt class="text-sm font-medium text-gray-900">描述</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ user?.spec?.bio || "无" }} {{ user?.user.spec?.bio || "无" }}
</dd> </dd>
</div> </div>
<div <div
@ -83,7 +78,7 @@ const router = useRouter();
<dd <dd
class="mt-1 text-sm tabular-nums text-gray-900 sm:col-span-3 sm:mt-0" class="mt-1 text-sm tabular-nums text-gray-900 sm:col-span-3 sm:mt-0"
> >
{{ formatDatetime(user?.metadata?.creationTimestamp) }} {{ formatDatetime(user?.user.metadata?.creationTimestamp) }}
</dd> </dd>
</div> </div>
<!-- TODO: add display last login time support --> <!-- TODO: add display last login time support -->
@ -93,7 +88,7 @@ const router = useRouter();
> >
<dt class="text-sm font-medium text-gray-900">最近登录时间</dt> <dt class="text-sm font-medium text-gray-900">最近登录时间</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ user?.metadata?.creationTimestamp }} {{ user?.user.metadata?.creationTimestamp }}
</dd> </dd>
</div> </div>
</dl> </dl>

View File

@ -25,13 +25,12 @@ import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
import GrantPermissionModal from "./components/GrantPermissionModal.vue"; import GrantPermissionModal from "./components/GrantPermissionModal.vue";
import { computed, onMounted, onUnmounted, ref, watch } from "vue"; import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import type { Role, User, UserList } from "@halo-dev/api-client"; import type { Role, User, ListedUserList } from "@halo-dev/api-client";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { useRoleStore } from "@/stores/role";
import { getNode } from "@formkit/core"; import { getNode } from "@formkit/core";
import FilterTag from "@/components/filter/FilterTag.vue"; import FilterTag from "@/components/filter/FilterTag.vue";
import { useFetchRole } from "../roles/composables/use-role"; import { useFetchRole } from "../roles/composables/use-role";
@ -44,7 +43,7 @@ const editingModal = ref<boolean>(false);
const passwordChangeModal = ref<boolean>(false); const passwordChangeModal = ref<boolean>(false);
const grantPermissionModal = ref<boolean>(false); const grantPermissionModal = ref<boolean>(false);
const users = ref<UserList>({ const users = ref<ListedUserList>({
page: 1, page: 1,
size: 20, size: 20,
total: 0, total: 0,
@ -62,7 +61,6 @@ const keyword = ref("");
const refreshInterval = ref(); const refreshInterval = ref();
const userStore = useUserStore(); const userStore = useUserStore();
const roleStore = useRoleStore();
const ANONYMOUSUSER_NAME = "anonymousUser"; const ANONYMOUSUSER_NAME = "anonymousUser";
const DELETEDUSER_NAME = "ghost"; const DELETEDUSER_NAME = "ghost";
@ -99,7 +97,7 @@ const handleFetchUsers = async (options?: {
users.value = data; users.value = data;
const deletedUsers = users.value.items.filter( const deletedUsers = users.value.items.filter(
(user) => !!user.metadata.deletionTimestamp (user) => !!user.user.metadata.deletionTimestamp
); );
if (deletedUsers.length) { if (deletedUsers.length) {
@ -188,7 +186,7 @@ const handleCheckAllChange = (e: Event) => {
if (checked) { if (checked) {
selectedUserNames.value = selectedUserNames.value =
users.value.items.map((user) => { users.value.items.map((user) => {
return user.metadata.name; return user.user.metadata.name;
}) || []; }) || [];
} else { } else {
selectedUserNames.value.length = 0; selectedUserNames.value.length = 0;
@ -215,16 +213,6 @@ const handleOpenGrantPermissionModal = (user: User) => {
grantPermissionModal.value = true; grantPermissionModal.value = true;
}; };
const getRoles = (user: User) => {
const names = JSON.parse(
user.metadata.annotations?.[rbacAnnotations.ROLE_NAMES] || "[]"
);
return names.map((name: string) => {
return roleStore.getRoleDisplayName(name);
});
};
onMounted(() => { onMounted(() => {
handleFetchUsers(); handleFetchUsers();
}); });
@ -521,14 +509,14 @@ const hasFilters = computed(() => {
role="list" role="list"
> >
<li v-for="(user, index) in users.items" :key="index"> <li v-for="(user, index) in users.items" :key="index">
<VEntity :is-selected="checkSelection(user)"> <VEntity :is-selected="checkSelection(user.user)">
<template <template
v-if="currentUserHasPermission(['system:users:manage'])" v-if="currentUserHasPermission(['system:users:manage'])"
#checkbox #checkbox
> >
<input <input
v-model="selectedUserNames" v-model="selectedUserNames"
:value="user.metadata.name" :value="user.user.metadata.name"
class="h-4 w-4 rounded border-gray-300 text-indigo-600" class="h-4 w-4 rounded border-gray-300 text-indigo-600"
name="post-checkbox" name="post-checkbox"
type="checkbox" type="checkbox"
@ -538,18 +526,18 @@ const hasFilters = computed(() => {
<VEntityField> <VEntityField>
<template #description> <template #description>
<VAvatar <VAvatar
:alt="user.spec.displayName" :alt="user.user.spec.displayName"
:src="user.spec.avatar" :src="user.user.spec.avatar"
size="md" size="md"
></VAvatar> ></VAvatar>
</template> </template>
</VEntityField> </VEntityField>
<VEntityField <VEntityField
:title="user.spec.displayName" :title="user.user.spec.displayName"
:description="user.metadata.name" :description="user.user.metadata.name"
:route="{ :route="{
name: 'UserDetail', name: 'UserDetail',
params: { name: user.metadata.name }, params: { name: user.user.metadata.name },
}" }"
/> />
</template> </template>
@ -557,17 +545,21 @@ const hasFilters = computed(() => {
<VEntityField> <VEntityField>
<template #description> <template #description>
<div <div
v-for="(role, roleIndex) in getRoles(user)" v-for="(role, roleIndex) in user.roles"
:key="roleIndex" :key="roleIndex"
class="flex items-center" class="flex items-center"
> >
<VTag> <VTag>
{{ role }} {{
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || role.metadata.name
}}
</VTag> </VTag>
</div> </div>
</template> </template>
</VEntityField> </VEntityField>
<VEntityField v-if="user.metadata.deletionTimestamp"> <VEntityField v-if="user.user.metadata.deletionTimestamp">
<template #description> <template #description>
<VStatusDot v-tooltip="``" state="warning" animate /> <VStatusDot v-tooltip="``" state="warning" animate />
</template> </template>
@ -575,7 +567,7 @@ const hasFilters = computed(() => {
<VEntityField> <VEntityField>
<template #description> <template #description>
<span class="truncate text-xs tabular-nums text-gray-500"> <span class="truncate text-xs tabular-nums text-gray-500">
{{ formatDatetime(user.metadata.creationTimestamp) }} {{ formatDatetime(user.user.metadata.creationTimestamp) }}
</span> </span>
</template> </template>
</VEntityField> </VEntityField>
@ -588,35 +580,37 @@ const hasFilters = computed(() => {
v-close-popper v-close-popper
block block
type="secondary" type="secondary"
@click="handleOpenCreateModal(user)" @click="handleOpenCreateModal(user.user)"
> >
修改资料 修改资料
</VButton> </VButton>
<VButton <VButton
v-close-popper v-close-popper
block block
@click="handleOpenPasswordChangeModal(user)" @click="handleOpenPasswordChangeModal(user.user)"
> >
修改密码 修改密码
</VButton> </VButton>
<VButton <VButton
v-if=" v-if="
userStore.currentUser?.metadata.name !== user.metadata.name userStore.currentUser?.metadata.name !==
user.user.metadata.name
" "
v-close-popper v-close-popper
block block
@click="handleOpenGrantPermissionModal(user)" @click="handleOpenGrantPermissionModal(user.user)"
> >
分配角色 分配角色
</VButton> </VButton>
<VButton <VButton
v-if=" v-if="
userStore.currentUser?.metadata.name !== user.metadata.name userStore.currentUser?.metadata.name !==
user.user.metadata.name
" "
v-close-popper v-close-popper
block block
type="danger" type="danger"
@click="handleDelete(user)" @click="handleDelete(user.user)"
> >
删除 删除
</VButton> </VButton>

View File

@ -2,9 +2,9 @@
import BasicLayout from "@/layouts/BasicLayout.vue"; import BasicLayout from "@/layouts/BasicLayout.vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { VButton, VSpace, VTabbar, VAvatar } from "@halo-dev/components"; import { VButton, VSpace, VTabbar, VAvatar } from "@halo-dev/components";
import { computed, onMounted, provide, ref, watch } from "vue"; import { computed, onMounted, provide, ref, watch, type Ref } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import type { User } from "@halo-dev/api-client"; import type { DetailedUser } from "@halo-dev/api-client";
import UserEditingModal from "../components/UserEditingModal.vue"; import UserEditingModal from "../components/UserEditingModal.vue";
import UserPasswordChangeModal from "../components/UserPasswordChangeModal.vue"; import UserPasswordChangeModal from "../components/UserPasswordChangeModal.vue";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
@ -26,7 +26,7 @@ const tabs = [
// }, // },
]; ];
const user = ref<User>(); const user = ref<DetailedUser>();
const loading = ref(); const loading = ref();
const editingModal = ref(false); const editingModal = ref(false);
const passwordChangeModal = ref(false); const passwordChangeModal = ref(false);
@ -40,7 +40,7 @@ const handleFetchUser = async () => {
const { data } = await apiClient.user.getCurrentUserDetail(); const { data } = await apiClient.user.getCurrentUserDetail();
user.value = data; user.value = data;
} else { } else {
const { data } = await apiClient.extension.user.getv1alpha1User({ const { data } = await apiClient.user.getUserDetail({
name: params.name as string, name: params.name as string,
}); });
user.value = data; user.value = data;
@ -56,10 +56,12 @@ const isCurrentUser = computed(() => {
if (params.name === "-") { if (params.name === "-") {
return true; return true;
} }
return user.value?.metadata.name === userStore.currentUser?.metadata.name; return (
user.value?.user.metadata.name === userStore.currentUser?.metadata.name
);
}); });
provide("user", user); provide<Ref<DetailedUser | undefined>>("user", user);
const activeTab = ref(); const activeTab = ref();
@ -92,12 +94,12 @@ const handleTabChange = (id: string) => {
<BasicLayout> <BasicLayout>
<UserEditingModal <UserEditingModal
v-model:visible="editingModal" v-model:visible="editingModal"
:user="user" :user="user?.user"
@close="handleFetchUser" @close="handleFetchUser"
/> />
<UserPasswordChangeModal <UserPasswordChangeModal
v-model:visible="passwordChangeModal" v-model:visible="passwordChangeModal"
:user="user" :user="user?.user"
@close="handleFetchUser" @close="handleFetchUser"
/> />
<header class="bg-white"> <header class="bg-white">
@ -107,8 +109,8 @@ const handleTabChange = (id: string) => {
<div class="h-20 w-20"> <div class="h-20 w-20">
<VAvatar <VAvatar
v-if="user" v-if="user"
:src="user?.spec.avatar" :src="user.user.spec.avatar"
:alt="user?.spec.displayName" :alt="user.user.spec.displayName"
circle circle
width="100%" width="100%"
height="100%" height="100%"
@ -117,10 +119,10 @@ const handleTabChange = (id: string) => {
</div> </div>
<div class="block"> <div class="block">
<h1 class="truncate text-lg font-bold text-gray-900"> <h1 class="truncate text-lg font-bold text-gray-900">
{{ user?.spec.displayName }} {{ user?.user.spec.displayName }}
</h1> </h1>
<span v-if="!loading" class="text-sm text-gray-600"> <span v-if="!loading" class="text-sm text-gray-600">
@{{ user?.metadata.name }} @{{ user?.user.metadata.name }}
</span> </span>
</div> </div>
</div> </div>

View File

@ -1,37 +1,12 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import type { Role, UserPermission } from "@halo-dev/api-client"; import type { UserPermission } from "@halo-dev/api-client";
import { ref } from "vue"; import { ref } from "vue";
import { apiClient } from "@/utils/api-client";
import { roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations";
export const useRoleStore = defineStore("role", () => { export const useRoleStore = defineStore("role", () => {
const roles = ref<Role[]>([]);
const permissions = ref<UserPermission>({ const permissions = ref<UserPermission>({
roles: [], roles: [],
uiPermissions: [], uiPermissions: [],
}); });
async function fetchRoles() { return { permissions };
try {
const { data } = await apiClient.extension.role.listv1alpha1Role(
{
page: 0,
size: 0,
labelSelector: [`!${roleLabels.TEMPLATE}`],
},
{ mute: true }
);
roles.value = data.items;
} catch (error) {
console.error("Failed to fetch roles", error);
}
}
function getRoleDisplayName(name: string) {
const role = roles.value.find((role) => role.metadata.name === name);
return role?.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] || name;
}
return { roles, permissions, fetchRoles, getRoleDisplayName };
}); });

View File

@ -1,9 +1,10 @@
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import type { User } from "@halo-dev/api-client"; import type { Role, User } from "@halo-dev/api-client";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
interface UserStoreState { interface UserStoreState {
currentUser?: User; currentUser?: User;
currentRoles?: Role[];
isAnonymous: boolean; isAnonymous: boolean;
loginModalVisible: boolean; loginModalVisible: boolean;
} }
@ -11,6 +12,7 @@ interface UserStoreState {
export const useUserStore = defineStore("user", { export const useUserStore = defineStore("user", {
state: (): UserStoreState => ({ state: (): UserStoreState => ({
currentUser: undefined, currentUser: undefined,
currentRoles: [],
isAnonymous: true, isAnonymous: true,
loginModalVisible: false, loginModalVisible: false,
}), }),
@ -18,8 +20,9 @@ export const useUserStore = defineStore("user", {
async fetchCurrentUser() { async fetchCurrentUser() {
try { try {
const { data } = await apiClient.user.getCurrentUserDetail(); const { data } = await apiClient.user.getCurrentUserDetail();
this.currentUser = data; this.currentUser = data.user;
this.isAnonymous = data.metadata.name === "anonymousUser"; this.currentRoles = data.roles;
this.isAnonymous = data.user.metadata.name === "anonymousUser";
} catch (e) { } catch (e) {
console.error("Failed to fetch current user", e); console.error("Failed to fetch current user", e);
} }