From fc77d51c488c18eebf65ad06820349ff87136a11 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:19:42 +0800 Subject: [PATCH] refactor: add custom API for create user (#3803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.5.x /kind api-change #### What this PR does / why we need it: 提供自定义 API 用于创建用户账号 简化了创建用户账号需要先创建账号,再分配角色再重置密码的复杂流程。 需要 Console 端适配此 PR #### Which issue(s) this PR fixes: Fixes #2852 #### Does this PR introduce a user-facing change? ```release-note 优化用户账号创建流程 ``` --- .../core/extension/endpoint/UserEndpoint.java | 90 +++++++- .../core/extension/service/UserService.java | 2 + .../extension/service/UserServiceImpl.java | 24 ++- .../extensions/role-template-user.yaml | 3 + .../extension/endpoint/UserEndpointTest.java | 23 +- .../service/UserServiceImplTest.java | 1 + console/packages/api-client/package.json | 2 +- .../api-client/src/.openapi-generator/FILES | 3 +- .../api-console-halo-run-v1alpha1-user-api.ts | 199 +++++++++++++++--- .../src/models/create-user-request.ts | 75 +++++++ .../packages/api-client/src/models/index.ts | 3 +- .../models/user-endpoint-listed-user-list.ts | 79 +++++++ console/src/modules/system/users/UserList.vue | 23 +- .../users/components/UserCreationModal.vue | 196 +++++++++++++++++ .../users/components/UserEditingModal.vue | 46 ++-- .../components/UserPasswordChangeModal.vue | 2 +- .../users/layouts/UserProfileLayout.vue | 8 +- 17 files changed, 677 insertions(+), 102 deletions(-) create mode 100644 console/packages/api-client/src/models/create-user-request.ts create mode 100644 console/packages/api-client/src/models/user-endpoint-listed-user-list.ts create mode 100644 console/src/modules/system/users/components/UserCreationModal.vue diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/UserEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/UserEndpoint.java index e8271fbed..45b94a49a 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/UserEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/UserEndpoint.java @@ -2,6 +2,8 @@ package run.halo.app.core.extension.endpoint; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; import static java.util.Comparator.comparing; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; @@ -13,8 +15,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -28,6 +32,7 @@ import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; @@ -43,12 +48,14 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; import run.halo.app.core.extension.Role; import run.halo.app.core.extension.User; import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.UserService; import run.halo.app.extension.Comparators; import run.halo.app.extension.ListResult; +import run.halo.app.extension.Metadata; import run.halo.app.extension.MetadataUtil; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.router.IListRequest; @@ -99,6 +106,14 @@ public class UserEndpoint implements CustomEndpoint { .required(true) .implementation(GrantRequest.class)) .response(responseBuilder().implementation(User.class))) + .POST("/users", this::createUser, + builder -> builder.operationId("CreateUser") + .description("Creates a new user.") + .tag(tag) + .requestBody(requestBodyBuilder() + .required(true) + .implementation(CreateUserRequest.class)) + .response(responseBuilder().implementation(User.class))) .GET("/users/{name}/permissions", this::getUserPermission, builder -> builder.operationId("GetPermissions") .description("Get permissions of user") @@ -133,13 +148,77 @@ public class UserEndpoint implements CustomEndpoint { .build(); } + private Mono createUser(ServerRequest request) { + return request.bodyToMono(CreateUserRequest.class) + .doOnNext(createUserRequest -> { + if (StringUtils.isBlank(createUserRequest.name())) { + throw new ServerWebInputException("Name is required"); + } + if (StringUtils.isBlank(createUserRequest.email())) { + throw new ServerWebInputException("Email is required"); + } + }) + .flatMap(userRequest -> { + User newUser = CreateUserRequest.from(userRequest); + return userService.createUser(newUser, userRequest.roles()) + .then(Mono.defer(() -> userService.updateWithRawPassword(userRequest.name(), + userRequest.password())) + .retryWhen(Retry.backoff(5, Duration.ofMillis(100)) + .filter(OptimisticLockingFailureException.class::isInstance) + ) + ); + }) + .flatMap(user -> ServerResponse.ok() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(user) + ); + } + private Mono getUserByName(ServerRequest request) { final var name = request.pathVariable("name"); return userService.getUser(name) .flatMap(this::toDetailedUser) .flatMap(user -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) - .bodyValue(user)); + .bodyValue(user) + ); + } + + record CreateUserRequest(@Schema(requiredMode = REQUIRED) String name, + @Schema(requiredMode = REQUIRED) String email, + String displayName, + String avatar, + String phone, + String password, + String bio, + Map annotations, + Set roles) { + + /** + *

Creates a new user from {@link CreateUserRequest}.

+ * Note: this method will not set password. + * + * @param userRequest user request + * @return user from request + */ + public static User from(CreateUserRequest userRequest) { + var user = new User(); + user.setMetadata(new Metadata()); + user.getMetadata().setName(userRequest.name()); + user.getMetadata().setAnnotations(new HashMap<>()); + Map annotations = + defaultIfNull(userRequest.annotations(), Map.of()); + user.getMetadata().getAnnotations().putAll(annotations); + + var spec = new User.UserSpec(); + user.setSpec(spec); + spec.setEmail(userRequest.email()); + spec.setDisplayName(defaultIfBlank(userRequest.displayName(), userRequest.name())); + spec.setAvatar(userRequest.avatar()); + spec.setPhone(userRequest.phone()); + spec.setBio(userRequest.bio()); + return user; + } } private Mono updateProfile(ServerRequest request) { @@ -165,7 +244,8 @@ public class UserEndpoint implements CustomEndpoint { spec.setEmail(newSpec.getEmail()); spec.setPhone(newSpec.getPhone()); return currentUser; - })) + }) + ) .flatMap(client::update) .flatMap(updatedUser -> ServerResponse.ok().bodyValue(updatedUser)); } @@ -240,12 +320,6 @@ public class UserEndpoint implements CustomEndpoint { .then(ServerResponse.ok().build())); } - private Mono checkRoles(GrantRequest request) { - return Flux.fromIterable(request.roles) - .flatMap(role -> client.get(Role.class, role)) - .then(Mono.just(request)); - } - record GrantRequest(Set roles) { } diff --git a/application/src/main/java/run/halo/app/core/extension/service/UserService.java b/application/src/main/java/run/halo/app/core/extension/service/UserService.java index dfed6837b..ad81163eb 100644 --- a/application/src/main/java/run/halo/app/core/extension/service/UserService.java +++ b/application/src/main/java/run/halo/app/core/extension/service/UserService.java @@ -21,4 +21,6 @@ public interface UserService { Mono grantRoles(String username, Set roles); Mono signUp(User user, String password); + + Mono createUser(User user, Set roles); } diff --git a/application/src/main/java/run/halo/app/core/extension/service/UserServiceImpl.java b/application/src/main/java/run/halo/app/core/extension/service/UserServiceImpl.java index fce44104c..97672c524 100644 --- a/application/src/main/java/run/halo/app/core/extension/service/UserServiceImpl.java +++ b/application/src/main/java/run/halo/app/core/extension/service/UserServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.core.extension.Role; @@ -145,12 +146,14 @@ public class UserServiceImpl implements UserService { } String encodedPassword = passwordEncoder.encode(password); user.getSpec().setPassword(encodedPassword); - return createNewUser(user, defaultRole); + return createUser(user, Set.of(defaultRole)); }); } - private Mono createNewUser(User user, String defaultRole) { + @Override + public Mono createUser(User user, Set roleNames) { Assert.notNull(user, "User must not be null"); + Assert.notNull(roleNames, "Roles must not be null"); return client.fetch(User.class, user.getMetadata().getName()) .hasElement() .flatMap(hasUser -> { @@ -160,10 +163,17 @@ public class UserServiceImpl implements UserService { "problemDetail.user.duplicateName", new Object[] {user.getMetadata().getName()})); } - return client.create(user) - .flatMap(newUser -> grantRoles(user.getMetadata().getName(), - Set.of(defaultRole)) - ); - }); + // Check if all roles exist + return Flux.fromIterable(roleNames) + .flatMap(roleName -> client.fetch(Role.class, roleName) + .switchIfEmpty(Mono.error(() -> new ServerWebInputException( + "Role [" + roleName + "] is not found.")) + ) + ) + .then(); + }) + .then(Mono.defer(() -> client.create(user) + .flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames))) + ); } } diff --git a/application/src/main/resources/extensions/role-template-user.yaml b/application/src/main/resources/extensions/role-template-user.yaml index 8fdedff3f..0d2267bea 100644 --- a/application/src/main/resources/extensions/role-template-user.yaml +++ b/application/src/main/resources/extensions/role-template-user.yaml @@ -15,6 +15,9 @@ rules: - apiGroups: [ "" ] resources: [ "users" ] verbs: [ "create", "patch", "update", "delete", "deletecollection" ] + - apiGroups: [ "api.console.halo.run" ] + resources: [ "users", "users/permissions", "users/password" ] + verbs: [ "*" ] --- apiVersion: v1alpha1 kind: "Role" diff --git a/application/src/test/java/run/halo/app/core/extension/endpoint/UserEndpointTest.java b/application/src/test/java/run/halo/app/core/extension/endpoint/UserEndpointTest.java index 70ccabd5d..04c751f1f 100644 --- a/application/src/test/java/run/halo/app/core/extension/endpoint/UserEndpointTest.java +++ b/application/src/test/java/run/halo/app/core/extension/endpoint/UserEndpointTest.java @@ -3,6 +3,7 @@ package run.halo.app.core.extension.endpoint; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; @@ -491,4 +492,24 @@ class UserEndpointTest { } } -} \ No newline at end of file + @Test + void createWhenNameDuplicate() { + when(userService.createUser(any(User.class), anySet())) + .thenReturn(Mono.just(new User())); + when(userService.updateWithRawPassword(anyString(), anyString())) + .thenReturn(Mono.just(new User())); + var userRequest = new UserEndpoint.CreateUserRequest("fake-user", + "fake-email", + "", + "", + "", + "", + "", + Map.of(), + Set.of()); + webClient.post().uri("/users") + .bodyValue(userRequest) + .exchange() + .expectStatus().isOk(); + } +} diff --git a/application/src/test/java/run/halo/app/core/extension/service/UserServiceImplTest.java b/application/src/test/java/run/halo/app/core/extension/service/UserServiceImplTest.java index b34bceaf5..a3002641b 100644 --- a/application/src/test/java/run/halo/app/core/extension/service/UserServiceImplTest.java +++ b/application/src/test/java/run/halo/app/core/extension/service/UserServiceImplTest.java @@ -458,6 +458,7 @@ class UserServiceImplTest { User fakeUser = fakeSignUpUser("fake-user", "fake-password"); + when(client.fetch(eq(Role.class), anyString())).thenReturn(Mono.just(new Role())); when(client.create(any(User.class))).thenReturn(Mono.just(fakeUser)); UserServiceImpl spyUserService = spy(userService); doReturn(Mono.just(fakeUser)).when(spyUserService).grantRoles(eq("fake-user"), diff --git a/console/packages/api-client/package.json b/console/packages/api-client/package.json index 1022f4a32..997cb521c 100644 --- a/console/packages/api-client/package.json +++ b/console/packages/api-client/package.json @@ -9,7 +9,7 @@ "prettier": "prettier --write './src/**/*.{js,jsx,ts,tsx,json,yml,yaml}'", "typecheck": "tsc --noEmit", "release": "bumpp", - "gen": "openapi-generator-cli generate -i http://localhost:8090/v3/api-docs/all-api -g typescript-axios -c ./src/.openapi_config.yaml -o ./src --type-mappings='set=Array' && pnpm lint && pnpm prettier" + "gen": "openapi-generator-cli generate -i http://localhost:8090/v3/api-docs/all-api -g typescript-axios -c ./src/.openapi_config.yaml -o ./src --type-mappings='set=Array' --skip-validate-spec && pnpm lint && pnpm prettier" }, "keywords": [], "author": "@halo-dev", diff --git a/console/packages/api-client/src/.openapi-generator/FILES b/console/packages/api-client/src/.openapi-generator/FILES index 80d4bb9b7..8008a9fdb 100644 --- a/console/packages/api-client/src/.openapi-generator/FILES +++ b/console/packages/api-client/src/.openapi-generator/FILES @@ -89,6 +89,7 @@ models/contributor.ts models/counter-list.ts models/counter-request.ts models/counter.ts +models/create-user-request.ts models/custom-templates.ts models/dashboard-stats.ts models/detailed-user.ts @@ -118,7 +119,6 @@ models/listed-reply-list.ts models/listed-reply.ts models/listed-single-page-list.ts models/listed-single-page.ts -models/listed-user-list.ts models/listed-user.ts models/login-history.ts models/menu-item-list.ts @@ -201,6 +201,7 @@ models/theme.ts models/user-connection-list.ts models/user-connection-spec.ts models/user-connection.ts +models/user-endpoint-listed-user-list.ts models/user-list.ts models/user-permission.ts models/user-spec.ts diff --git a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-user-api.ts b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-user-api.ts index febee8584..769978418 100644 --- a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-user-api.ts +++ b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-user-api.ts @@ -40,14 +40,16 @@ import { // @ts-ignore import { ChangePasswordRequest } from "../models"; // @ts-ignore +import { CreateUserRequest } from "../models"; +// @ts-ignore import { DetailedUser } from "../models"; // @ts-ignore import { GrantRequest } from "../models"; // @ts-ignore -import { ListedUserList } from "../models"; -// @ts-ignore import { User } from "../models"; // @ts-ignore +import { UserEndpointListedUserList } from "../models"; +// @ts-ignore import { UserPermission } from "../models"; /** * ApiConsoleHaloRunV1alpha1UserApi - axios parameter creator @@ -126,6 +128,63 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function ( options: localVarRequestOptions, }; }, + /** + * Creates a new user. + * @param {CreateUserRequest} createUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createUser: async ( + createUserRequest: CreateUserRequest, + options: AxiosRequestConfig = {} + ): Promise => { + // verify required parameter 'createUserRequest' is not null or undefined + assertParamExists("createUser", "createUserRequest", createUserRequest); + const localVarPath = `/apis/api.console.halo.run/v1alpha1/users`; + // 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: "POST", + ...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); + + localVarHeaderParameter["Content-Type"] = "application/json"; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + localVarRequestOptions.data = serializeDataIfNeeded( + createUserRequest, + localVarRequestOptions, + configuration + ); + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Get current user detail * @param {*} [options] Override http request option. @@ -348,23 +407,23 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function ( /** * List users * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp - * @param {string} [keyword] * @param {string} [role] + * @param {string} [keyword] * @param {number} [size] Size of one page. Zero indicates no limit. + * @param {number} [page] The page number. Zero indicates no page. * @param {Array} [labelSelector] Label selector for filtering. * @param {Array} [fieldSelector] Field selector for filtering. - * @param {number} [page] The page number. Zero indicates no page. * @param {*} [options] Override http request option. * @throws {RequiredError} */ listUsers: async ( sort?: Array, - keyword?: string, role?: string, + keyword?: string, size?: number, + page?: number, labelSelector?: Array, fieldSelector?: Array, - page?: number, options: AxiosRequestConfig = {} ): Promise => { const localVarPath = `/apis/api.console.halo.run/v1alpha1/users`; @@ -395,18 +454,22 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function ( localVarQueryParameter["sort"] = Array.from(sort); } - if (keyword !== undefined) { - localVarQueryParameter["keyword"] = keyword; - } - if (role !== undefined) { localVarQueryParameter["role"] = role; } + if (keyword !== undefined) { + localVarQueryParameter["keyword"] = keyword; + } + if (size !== undefined) { localVarQueryParameter["size"] = size; } + if (page !== undefined) { + localVarQueryParameter["page"] = page; + } + if (labelSelector) { localVarQueryParameter["labelSelector"] = labelSelector; } @@ -415,10 +478,6 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function ( localVarQueryParameter["fieldSelector"] = fieldSelector; } - if (page !== undefined) { - localVarQueryParameter["page"] = page; - } - setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -529,6 +588,29 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function ( configuration ); }, + /** + * Creates a new user. + * @param {CreateUserRequest} createUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createUser( + createUserRequest: CreateUserRequest, + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.createUser( + createUserRequest, + options + ); + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ); + }, /** * Get current user detail * @param {*} [options] Override http request option. @@ -623,35 +705,38 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function ( /** * List users * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp - * @param {string} [keyword] * @param {string} [role] + * @param {string} [keyword] * @param {number} [size] Size of one page. Zero indicates no limit. + * @param {number} [page] The page number. Zero indicates no page. * @param {Array} [labelSelector] Label selector for filtering. * @param {Array} [fieldSelector] Field selector for filtering. - * @param {number} [page] The page number. Zero indicates no page. * @param {*} [options] Override http request option. * @throws {RequiredError} */ async listUsers( sort?: Array, - keyword?: string, role?: string, + keyword?: string, size?: number, + page?: number, labelSelector?: Array, fieldSelector?: Array, - page?: number, options?: AxiosRequestConfig ): Promise< - (axios?: AxiosInstance, basePath?: string) => AxiosPromise + ( + axios?: AxiosInstance, + basePath?: string + ) => AxiosPromise > { const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers( sort, - keyword, role, + keyword, size, + page, labelSelector, fieldSelector, - page, options ); return createRequestFunction( @@ -714,6 +799,20 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function ( ) .then((request) => request(axios, basePath)); }, + /** + * Creates a new user. + * @param {ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createUser( + requestParameters: ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest, + options?: AxiosRequestConfig + ): AxiosPromise { + return localVarFp + .createUser(requestParameters.createUserRequest, options) + .then((request) => request(axios, basePath)); + }, /** * Get current user detail * @param {*} [options] Override http request option. @@ -781,16 +880,16 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function ( listUsers( requestParameters: ApiConsoleHaloRunV1alpha1UserApiListUsersRequest = {}, options?: AxiosRequestConfig - ): AxiosPromise { + ): AxiosPromise { return localVarFp .listUsers( requestParameters.sort, - requestParameters.keyword, requestParameters.role, + requestParameters.keyword, requestParameters.size, + requestParameters.page, requestParameters.labelSelector, requestParameters.fieldSelector, - requestParameters.page, options ) .then((request) => request(axios, basePath)); @@ -833,6 +932,20 @@ export interface ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest { readonly changePasswordRequest: ChangePasswordRequest; } +/** + * Request parameters for createUser operation in ApiConsoleHaloRunV1alpha1UserApi. + * @export + * @interface ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest + */ +export interface ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest { + /** + * + * @type {CreateUserRequest} + * @memberof ApiConsoleHaloRunV1alpha1UserApiCreateUser + */ + readonly createUserRequest: CreateUserRequest; +} + /** * Request parameters for getPermissions operation in ApiConsoleHaloRunV1alpha1UserApi. * @export @@ -900,14 +1013,14 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest { * @type {string} * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers */ - readonly keyword?: string; + readonly role?: string; /** * * @type {string} * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers */ - readonly role?: string; + readonly keyword?: string; /** * Size of one page. Zero indicates no limit. @@ -916,6 +1029,13 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest { */ readonly size?: number; + /** + * The page number. Zero indicates no page. + * @type {number} + * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers + */ + readonly page?: number; + /** * Label selector for filtering. * @type {Array} @@ -929,13 +1049,6 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest { * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers */ readonly fieldSelector?: Array; - - /** - * The page number. Zero indicates no page. - * @type {number} - * @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers - */ - readonly page?: number; } /** @@ -979,6 +1092,22 @@ export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI { .then((request) => request(this.axios, this.basePath)); } + /** + * Creates a new user. + * @param {ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiConsoleHaloRunV1alpha1UserApi + */ + public createUser( + requestParameters: ApiConsoleHaloRunV1alpha1UserApiCreateUserRequest, + options?: AxiosRequestConfig + ) { + return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration) + .createUser(requestParameters.createUserRequest, options) + .then((request) => request(this.axios, this.basePath)); + } + /** * Get current user detail * @param {*} [options] Override http request option. @@ -1057,12 +1186,12 @@ export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI { return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration) .listUsers( requestParameters.sort, - requestParameters.keyword, requestParameters.role, + requestParameters.keyword, requestParameters.size, + requestParameters.page, requestParameters.labelSelector, requestParameters.fieldSelector, - requestParameters.page, options ) .then((request) => request(this.axios, this.basePath)); diff --git a/console/packages/api-client/src/models/create-user-request.ts b/console/packages/api-client/src/models/create-user-request.ts new file mode 100644 index 000000000..f9e26d0cc --- /dev/null +++ b/console/packages/api-client/src/models/create-user-request.ts @@ -0,0 +1,75 @@ +/* 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. + */ + +/** + * + * @export + * @interface CreateUserRequest + */ +export interface CreateUserRequest { + /** + * + * @type {{ [key: string]: string; }} + * @memberof CreateUserRequest + */ + annotations?: { [key: string]: string }; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + avatar?: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + bio?: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + displayName?: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + email: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + name: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + password?: string; + /** + * + * @type {string} + * @memberof CreateUserRequest + */ + phone?: string; + /** + * + * @type {Array} + * @memberof CreateUserRequest + */ + roles?: Array; +} diff --git a/console/packages/api-client/src/models/index.ts b/console/packages/api-client/src/models/index.ts index b3f40b339..240391618 100644 --- a/console/packages/api-client/src/models/index.ts +++ b/console/packages/api-client/src/models/index.ts @@ -35,6 +35,7 @@ export * from "./contributor"; export * from "./counter"; export * from "./counter-list"; export * from "./counter-request"; +export * from "./create-user-request"; export * from "./custom-templates"; export * from "./dashboard-stats"; export * from "./detailed-user"; @@ -64,7 +65,6 @@ export * from "./listed-reply-list"; export * from "./listed-single-page"; export * from "./listed-single-page-list"; export * from "./listed-user"; -export * from "./listed-user-list"; export * from "./login-history"; export * from "./menu"; export * from "./menu-item"; @@ -147,6 +147,7 @@ export * from "./user"; export * from "./user-connection"; export * from "./user-connection-list"; export * from "./user-connection-spec"; +export * from "./user-endpoint-listed-user-list"; export * from "./user-list"; export * from "./user-permission"; export * from "./user-spec"; diff --git a/console/packages/api-client/src/models/user-endpoint-listed-user-list.ts b/console/packages/api-client/src/models/user-endpoint-listed-user-list.ts new file mode 100644 index 000000000..6582c90ef --- /dev/null +++ b/console/packages/api-client/src/models/user-endpoint-listed-user-list.ts @@ -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 UserEndpointListedUserList + */ +export interface UserEndpointListedUserList { + /** + * Indicates whether current page is the first page. + * @type {boolean} + * @memberof UserEndpointListedUserList + */ + first: boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof UserEndpointListedUserList + */ + hasNext: boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof UserEndpointListedUserList + */ + hasPrevious: boolean; + /** + * A chunk of items. + * @type {Array} + * @memberof UserEndpointListedUserList + */ + items: Array; + /** + * Indicates whether current page is the last page. + * @type {boolean} + * @memberof UserEndpointListedUserList + */ + last: boolean; + /** + * Page number, starts from 1. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof UserEndpointListedUserList + */ + page: number; + /** + * Size of each page. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof UserEndpointListedUserList + */ + size: number; + /** + * Total elements. + * @type {number} + * @memberof UserEndpointListedUserList + */ + total: number; + /** + * Indicates total pages. + * @type {number} + * @memberof UserEndpointListedUserList + */ + totalPages: number; +} diff --git a/console/src/modules/system/users/UserList.vue b/console/src/modules/system/users/UserList.vue index 7505806ad..6ee9c6b32 100644 --- a/console/src/modules/system/users/UserList.vue +++ b/console/src/modules/system/users/UserList.vue @@ -40,12 +40,14 @@ import { useFetchRole } from "../roles/composables/use-role"; import FilterCleanButton from "@/components/filter/FilterCleanButton.vue"; import { useQuery } from "@tanstack/vue-query"; import { useI18n } from "vue-i18n"; +import UserCreationModal from "./components/UserCreationModal.vue"; const { currentUserHasPermission } = usePermission(); const { t } = useI18n(); const checkedAll = ref(false); const editingModal = ref(false); +const creationModal = ref(false); const passwordChangeModal = ref(false); const grantPermissionModal = ref(false); @@ -233,11 +235,6 @@ const handleOpenCreateModal = (user: User) => { editingModal.value = true; }; -const onEditingModalClose = () => { - routeQueryAction.value = undefined; - refetch(); -}; - const handleOpenPasswordChangeModal = (user: User) => { selectedUser.value = user; passwordChangeModal.value = true; @@ -252,19 +249,17 @@ const handleOpenGrantPermissionModal = (user: User) => { const routeQueryAction = useRouteQuery("action"); onMounted(() => { - if (!routeQueryAction.value) { - return; - } if (routeQueryAction.value === "create") { - editingModal.value = true; + creationModal.value = true; } });