mirror of https://github.com/halo-dev/halo
refactor: add custom API for create user (#3803)
#### 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 优化用户账号创建流程 ```pull/3838/head
parent
6ca2cabffb
commit
fc77d51c48
|
@ -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<ServerResponse> 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<ServerResponse> 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<String, String> annotations,
|
||||
Set<String> roles) {
|
||||
|
||||
/**
|
||||
* <p>Creates a new user from {@link CreateUserRequest}.</p>
|
||||
* 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<String, String> 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<ServerResponse> 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<GrantRequest> checkRoles(GrantRequest request) {
|
||||
return Flux.fromIterable(request.roles)
|
||||
.flatMap(role -> client.get(Role.class, role))
|
||||
.then(Mono.just(request));
|
||||
}
|
||||
|
||||
record GrantRequest(Set<String> roles) {
|
||||
}
|
||||
|
||||
|
|
|
@ -21,4 +21,6 @@ public interface UserService {
|
|||
Mono<User> grantRoles(String username, Set<String> roles);
|
||||
|
||||
Mono<User> signUp(User user, String password);
|
||||
|
||||
Mono<User> createUser(User user, Set<String> roles);
|
||||
}
|
||||
|
|
|
@ -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<User> createNewUser(User user, String defaultRole) {
|
||||
@Override
|
||||
public Mono<User> createUser(User user, Set<String> 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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<RequestArgs> => {
|
||||
// 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<string>} [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<string>} [labelSelector] Label 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.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listUsers: async (
|
||||
sort?: Array<string>,
|
||||
keyword?: string,
|
||||
role?: string,
|
||||
keyword?: string,
|
||||
size?: number,
|
||||
page?: number,
|
||||
labelSelector?: Array<string>,
|
||||
fieldSelector?: Array<string>,
|
||||
page?: number,
|
||||
options: AxiosRequestConfig = {}
|
||||
): Promise<RequestArgs> => {
|
||||
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<User>
|
||||
> {
|
||||
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<string>} [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<string>} [labelSelector] Label 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.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listUsers(
|
||||
sort?: Array<string>,
|
||||
keyword?: string,
|
||||
role?: string,
|
||||
keyword?: string,
|
||||
size?: number,
|
||||
page?: number,
|
||||
labelSelector?: Array<string>,
|
||||
fieldSelector?: Array<string>,
|
||||
page?: number,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<
|
||||
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedUserList>
|
||||
(
|
||||
axios?: AxiosInstance,
|
||||
basePath?: string
|
||||
) => AxiosPromise<UserEndpointListedUserList>
|
||||
> {
|
||||
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<User> {
|
||||
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<ListedUserList> {
|
||||
): AxiosPromise<UserEndpointListedUserList> {
|
||||
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<string>}
|
||||
|
@ -929,13 +1049,6 @@ export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest {
|
|||
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||
*/
|
||||
readonly fieldSelector?: Array<string>;
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
|
|
@ -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<string>}
|
||||
* @memberof CreateUserRequest
|
||||
*/
|
||||
roles?: Array<string>;
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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<ListedUser>}
|
||||
* @memberof UserEndpointListedUserList
|
||||
*/
|
||||
items: Array<ListedUser>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
|
@ -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<boolean>(false);
|
||||
const creationModal = ref<boolean>(false);
|
||||
const passwordChangeModal = ref<boolean>(false);
|
||||
const grantPermissionModal = ref<boolean>(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<string | undefined>("action");
|
||||
|
||||
onMounted(() => {
|
||||
if (!routeQueryAction.value) {
|
||||
return;
|
||||
}
|
||||
if (routeQueryAction.value === "create") {
|
||||
editingModal.value = true;
|
||||
creationModal.value = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<UserEditingModal
|
||||
v-model:visible="editingModal"
|
||||
:user="selectedUser"
|
||||
@close="onEditingModalClose"
|
||||
<UserEditingModal v-model:visible="editingModal" :user="selectedUser" />
|
||||
|
||||
<UserCreationModal
|
||||
v-model:visible="creationModal"
|
||||
@close="routeQueryAction = undefined"
|
||||
/>
|
||||
|
||||
<UserPasswordChangeModal
|
||||
|
@ -305,7 +300,7 @@ onMounted(() => {
|
|||
<VButton
|
||||
v-permission="['system:users:manage']"
|
||||
type="secondary"
|
||||
@click="editingModal = true"
|
||||
@click="creationModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
<script lang="ts" setup>
|
||||
// core libs
|
||||
import { ref, watch } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import type { CreateUserRequest } from "@halo-dev/api-client";
|
||||
|
||||
// components
|
||||
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||
import SubmitButton from "@/components/button/SubmitButton.vue";
|
||||
|
||||
// libs
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { reset } from "@formkit/core";
|
||||
|
||||
// hooks
|
||||
import { setFocus } from "@/formkit/utils/focus";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:visible", visible: boolean): void;
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const initialFormState: CreateUserRequest = {
|
||||
avatar: "",
|
||||
bio: "",
|
||||
displayName: "",
|
||||
email: "",
|
||||
name: "",
|
||||
password: "",
|
||||
phone: "",
|
||||
roles: [],
|
||||
};
|
||||
|
||||
const formState = ref<CreateUserRequest>(cloneDeep(initialFormState));
|
||||
const selectedRole = ref("");
|
||||
const saving = ref(false);
|
||||
|
||||
const handleResetForm = () => {
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
reset("user-creation-form");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
setFocus("creationUserNameInput");
|
||||
} else {
|
||||
handleResetForm();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateUser = async () => {
|
||||
try {
|
||||
saving.value = true;
|
||||
|
||||
if (selectedRole.value) {
|
||||
formState.value.roles = [selectedRole.value];
|
||||
}
|
||||
|
||||
await apiClient.user.createUser({
|
||||
createUserRequest: formState.value,
|
||||
});
|
||||
|
||||
onVisibleChange(false);
|
||||
|
||||
Toast.success(t("core.common.toast.save_success"));
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||
} catch (e) {
|
||||
console.error("Failed to create or update user", e);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:title="$t('core.user.editing_modal.titles.create')"
|
||||
:visible="visible"
|
||||
:width="650"
|
||||
@update:visible="onVisibleChange"
|
||||
>
|
||||
<FormKit
|
||||
id="user-creation-form"
|
||||
name="user-creation-form"
|
||||
:config="{ validationVisibility: 'submit' }"
|
||||
type="form"
|
||||
@submit="handleCreateUser"
|
||||
>
|
||||
<FormKit
|
||||
id="creationUserNameInput"
|
||||
v-model="formState.name"
|
||||
:label="$t('core.user.editing_modal.fields.username.label')"
|
||||
type="text"
|
||||
name="name"
|
||||
:validation="[
|
||||
['required'],
|
||||
['length:0,63'],
|
||||
[
|
||||
'matches',
|
||||
/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/,
|
||||
],
|
||||
]"
|
||||
:validation-messages="{
|
||||
matches: $t('core.user.editing_modal.fields.username.validation'),
|
||||
}"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.displayName"
|
||||
:label="$t('core.user.editing_modal.fields.display_name.label')"
|
||||
type="text"
|
||||
name="displayName"
|
||||
validation="required|length:0,50"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.email"
|
||||
:label="$t('core.user.editing_modal.fields.email.label')"
|
||||
type="email"
|
||||
name="email"
|
||||
validation="required|email|length:0,100"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.phone"
|
||||
:label="$t('core.user.editing_modal.fields.phone.label')"
|
||||
type="text"
|
||||
name="phone"
|
||||
validation="length:0,20"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.avatar"
|
||||
:label="$t('core.user.editing_modal.fields.avatar.label')"
|
||||
type="attachment"
|
||||
name="avatar"
|
||||
validation="url|length:0,1024"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.password"
|
||||
:label="$t('core.user.change_password_modal.fields.new_password.label')"
|
||||
type="password"
|
||||
name="password"
|
||||
validation="required|length:0,100"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="selectedRole"
|
||||
:label="$t('core.user.grant_permission_modal.fields.role.label')"
|
||||
type="roleSelect"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.bio"
|
||||
:label="$t('core.user.editing_modal.fields.bio.label')"
|
||||
type="textarea"
|
||||
name="bio"
|
||||
validation="length:0,2048"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
|
||||
<template #footer>
|
||||
<VSpace>
|
||||
<SubmitButton
|
||||
v-if="visible"
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
:text="$t('core.common.buttons.submit')"
|
||||
@submit="$formkit.submit('user-creation-form')"
|
||||
>
|
||||
</SubmitButton>
|
||||
<VButton @click="onVisibleChange(false)">
|
||||
{{ $t("core.common.buttons.cancel_and_shortcut") }}
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VModal>
|
||||
</template>
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
// core libs
|
||||
import { computed, nextTick, ref, watch } from "vue";
|
||||
import { nextTick, ref, watch } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
|
||||
|
@ -17,9 +17,11 @@ import { setFocus } from "@/formkit/utils/focus";
|
|||
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -58,16 +60,6 @@ const initialFormState: User = {
|
|||
const formState = ref<User>(cloneDeep(initialFormState));
|
||||
const saving = ref(false);
|
||||
|
||||
const isUpdateMode = computed(() => {
|
||||
return !!formState.value.metadata.creationTimestamp;
|
||||
});
|
||||
|
||||
const creationModalTitle = computed(() => {
|
||||
return isUpdateMode.value
|
||||
? t("core.user.editing_modal.titles.update")
|
||||
: t("core.user.editing_modal.titles.create");
|
||||
});
|
||||
|
||||
const handleResetForm = () => {
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
reset("user-form");
|
||||
|
@ -78,7 +70,7 @@ watch(
|
|||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.user) formState.value = cloneDeep(props.user);
|
||||
setFocus(isUpdateMode.value ? "displayNameInput" : "userNameInput");
|
||||
setFocus("displayNameInput");
|
||||
} else {
|
||||
handleResetForm();
|
||||
}
|
||||
|
@ -94,7 +86,7 @@ const onVisibleChange = (visible: boolean) => {
|
|||
|
||||
const annotationsFormRef = ref<InstanceType<typeof AnnotationsForm>>();
|
||||
|
||||
const handleCreateUser = async () => {
|
||||
const handleUpdateUser = async () => {
|
||||
annotationsFormRef.value?.handleSubmit();
|
||||
await nextTick();
|
||||
|
||||
|
@ -111,25 +103,23 @@ const handleCreateUser = async () => {
|
|||
|
||||
try {
|
||||
saving.value = true;
|
||||
if (isUpdateMode.value) {
|
||||
if (props.user?.metadata.name === userStore.currentUser?.metadata.name) {
|
||||
await apiClient.user.updateCurrentUser({
|
||||
user: formState.value,
|
||||
});
|
||||
} else {
|
||||
await apiClient.extension.user.updatev1alpha1User({
|
||||
name: formState.value.metadata.name,
|
||||
user: formState.value,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.user?.metadata.name === userStore.currentUser?.metadata.name) {
|
||||
await apiClient.user.updateCurrentUser({
|
||||
user: formState.value,
|
||||
});
|
||||
} else {
|
||||
await apiClient.extension.user.createv1alpha1User({
|
||||
await apiClient.extension.user.updatev1alpha1User({
|
||||
name: formState.value.metadata.name,
|
||||
user: formState.value,
|
||||
});
|
||||
}
|
||||
|
||||
onVisibleChange(false);
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["user-detail"] });
|
||||
|
||||
Toast.success(t("core.common.toast.save_success"));
|
||||
} catch (e) {
|
||||
console.error("Failed to create or update user", e);
|
||||
|
@ -140,7 +130,7 @@ const handleCreateUser = async () => {
|
|||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:title="creationModalTitle"
|
||||
:title="$t('core.user.editing_modal.titles.update')"
|
||||
:visible="visible"
|
||||
:width="700"
|
||||
@update:visible="onVisibleChange"
|
||||
|
@ -150,7 +140,7 @@ const handleCreateUser = async () => {
|
|||
name="user-form"
|
||||
:config="{ validationVisibility: 'submit' }"
|
||||
type="form"
|
||||
@submit="handleCreateUser"
|
||||
@submit="handleUpdateUser"
|
||||
>
|
||||
<div>
|
||||
<div class="md:grid md:grid-cols-4 md:gap-6">
|
||||
|
@ -165,7 +155,7 @@ const handleCreateUser = async () => {
|
|||
<FormKit
|
||||
id="userNameInput"
|
||||
v-model="formState.metadata.name"
|
||||
:disabled="isUpdateMode"
|
||||
:disabled="true"
|
||||
:label="$t('core.user.editing_modal.fields.username.label')"
|
||||
type="text"
|
||||
name="name"
|
||||
|
|
|
@ -120,7 +120,7 @@ const handleChangePassword = async () => {
|
|||
"
|
||||
name="password_confirm"
|
||||
type="password"
|
||||
validation="required|confirm|length:0,50"
|
||||
validation="required|confirm|length:0,100"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<template #footer>
|
||||
|
|
|
@ -107,16 +107,14 @@ const handleTabChange = (id: string) => {
|
|||
</script>
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<UserEditingModal
|
||||
v-model:visible="editingModal"
|
||||
:user="user?.user"
|
||||
@close="refetch"
|
||||
/>
|
||||
<UserEditingModal v-model:visible="editingModal" :user="user?.user" />
|
||||
|
||||
<UserPasswordChangeModal
|
||||
v-model:visible="passwordChangeModal"
|
||||
:user="user?.user"
|
||||
@close="refetch"
|
||||
/>
|
||||
|
||||
<header class="bg-white">
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
|
|
Loading…
Reference in New Issue