diff --git a/src/portal/src/app/user/user.component.html b/src/portal/src/app/user/user.component.html index aeb2ca739..9e855c479 100644 --- a/src/portal/src/app/user/user.component.html +++ b/src/portal/src/app/user/user.component.html @@ -9,7 +9,7 @@
- + @@ -35,7 +35,7 @@ {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'USER.OF' | translate }} {{pagination.totalItems}} {{'USER.ITEMS' | translate }} - + diff --git a/src/portal/src/app/user/user.component.ts b/src/portal/src/app/user/user.component.ts index 91f3ec16c..e50455871 100644 --- a/src/portal/src/app/user/user.component.ts +++ b/src/portal/src/app/user/user.component.ts @@ -11,13 +11,19 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; import { Subscription, Observable, forkJoin } from "rxjs"; import { TranslateService } from '@ngx-translate/core'; import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; -import { operateChanges, OperateInfo, OperationService, OperationState, errorHandler as errorHandFn } from '@harbor/ui'; +import { + operateChanges, + OperateInfo, + OperationService, + OperationState, + errorHandler as errorHandFn, +} from '@harbor/ui'; import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service'; import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message'; import { MessageHandlerService } from '../shared/message-handler/message-handler.service'; @@ -41,348 +47,305 @@ import { throwError as observableThrowError } from "rxjs"; */ @Component({ - selector: 'harbor-user', - templateUrl: 'user.component.html', - styleUrls: ['user.component.scss'], - providers: [UserService], - changeDetection: ChangeDetectionStrategy.OnPush + selector: 'harbor-user', + templateUrl: 'user.component.html', + styleUrls: ['user.component.scss'], + providers: [UserService], }) export class UserComponent implements OnInit, OnDestroy { - users: User[] = []; - originalUsers: Observable; - selectedRow: User[] = []; - ISADMNISTRATOR: string = "USER.ENABLE_ADMIN_ACTION"; + users: User[] = []; + selectedRow: User[] = []; + ISADMNISTRATOR: string = "USER.ENABLE_ADMIN_ACTION"; - currentTerm: string; - totalCount: number = 0; - currentPage: number = 1; - timerHandler: any; + currentTerm: string; + totalCount: number = 0; + currentPage: number = 1; + pageSize: number = 15; + timerHandler: any; - private onGoing: boolean = true; - private adminMenuText: string = ""; - private adminColumn: string = ""; - private deletionSubscription: Subscription; - @ViewChild(NewUserModalComponent) - newUserDialog: NewUserModalComponent; - @ViewChild(ChangePasswordComponent) - changePwdDialog: ChangePasswordComponent; + private onGoing: boolean = true; + private adminMenuText: string = ""; + private adminColumn: string = ""; + private deletionSubscription: Subscription; + @ViewChild(NewUserModalComponent) + newUserDialog: NewUserModalComponent; + @ViewChild(ChangePasswordComponent) + changePwdDialog: ChangePasswordComponent; - constructor( - private userService: UserService, - private translate: TranslateService, - private deletionDialogService: ConfirmationDialogService, - private msgHandler: MessageHandlerService, - private session: SessionService, - private appConfigService: AppConfigService, - private operationService: OperationService, - private ref: ChangeDetectorRef) { - this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => { - if (confirmed && - confirmed.source === ConfirmationTargets.USER && - confirmed.state === ConfirmationState.CONFIRMED) { - this.delUser(confirmed.data); - } - }); - } - - isMySelf(uid: number): boolean { - let currentUser = this.session.getCurrentUser(); - if (currentUser) { - if (currentUser.user_id === uid) { - return true; - } + constructor( + private userService: UserService, + private translate: TranslateService, + private deletionDialogService: ConfirmationDialogService, + private msgHandler: MessageHandlerService, + private session: SessionService, + private appConfigService: AppConfigService, + private operationService: OperationService) { + this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => { + if (confirmed && + confirmed.source === ConfirmationTargets.USER && + confirmed.state === ConfirmationState.CONFIRMED) { + this.delUser(confirmed.data); + } + }); } - return false; - } + isMySelf(uid: number): boolean { + let currentUser = this.session.getCurrentUser(); + if (currentUser) { + if (currentUser.user_id === uid) { + return true; + } + } - get onlySelf(): boolean { - return this.selectedRow.length === 1 && this.isMySelf(this.selectedRow[0].user_id); - } - - public get canCreateUser(): boolean { - let appConfig = this.appConfigService.getConfig(); - if (appConfig) { - return !(appConfig.auth_mode === 'ldap_auth' || appConfig.auth_mode === 'uaa_auth' || appConfig.auth_mode === 'oidc_auth'); - } else { - return true; - } - } - - public get ifSameRole(): boolean { - let usersRole: number[] = []; - this.selectedRow.forEach(user => { - if (user.user_id === 0 || this.isMySelf(user.user_id)) { return false; - } - if (user.has_admin_role) { - usersRole.push(1); - } else { - usersRole.push(0); - } - }); - if (usersRole.length && usersRole.every(num => num === 0)) { - this.ISADMNISTRATOR = 'USER.ENABLE_ADMIN_ACTION'; - return true; - } - if (usersRole.length && usersRole.every(num => num === 1)) { - this.ISADMNISTRATOR = 'USER.DISABLE_ADMIN_ACTION'; - return true; - } - return false; - } - - isSystemAdmin(u: User): string { - if (!u) { - return "{{MISS}}"; - } - let key: string = u.has_admin_role ? "USER.IS_ADMIN" : "USER.IS_NOT_ADMIN"; - this.translate.get(key).subscribe((res: string) => this.adminColumn = res); - return this.adminColumn; - } - - adminActions(u: User): string { - if (!u) { - return "{{MISS}}"; - } - let key: string = u.has_admin_role ? "USER.DISABLE_ADMIN_ACTION" : "USER.ENABLE_ADMIN_ACTION"; - this.translate.get(key).subscribe((res: string) => this.adminMenuText = res); - return this.adminMenuText; - } - - public get inProgress(): boolean { - return this.onGoing; - } - - ngOnInit(): void { } - - ngOnDestroy(): void { - if (this.deletionSubscription) { - this.deletionSubscription.unsubscribe(); } - if (this.timerHandler) { - clearInterval(this.timerHandler); - this.timerHandler = null; - } - } - - openChangePwdModal(): void { - if (this.selectedRow.length === 1) { - this.changePwdDialog.open(this.selectedRow[0].user_id); + get onlySelf(): boolean { + return this.selectedRow.length === 1 && this.isMySelf(this.selectedRow[0].user_id); } - } + public get canCreateUser(): boolean { + let appConfig = this.appConfigService.getConfig(); + if (appConfig) { + return !(appConfig.auth_mode === 'ldap_auth' || appConfig.auth_mode === 'uaa_auth' || appConfig.auth_mode === 'oidc_auth'); + } else { + return true; + } + } - // Filter items by keywords - doFilter(terms: string): void { - this.selectedRow = []; - this.currentTerm = terms; - this.originalUsers.subscribe(users => { - if (terms.trim() === "") { - this.refreshUser((this.currentPage - 1) * 15, this.currentPage * 15); - } else { - let selectUsers = users.filter(user => { - return this.isMatchFilterTerm(terms, user.username); + public get ifSameRole(): boolean { + let usersRole: number[] = []; + this.selectedRow.forEach(user => { + if (user.user_id === 0 || this.isMySelf(user.user_id)) { + return false; + } + if (user.has_admin_role) { + usersRole.push(1); + } else { + usersRole.push(0); + } }); - this.totalCount = selectUsers.length; - this.users = selectUsers.slice((this.currentPage - 1) * 15, this.currentPage * 15); // First page - - this.forceRefreshView(5000); - } - }); - } - - // Disable the admin role for the specified user - changeAdminRole(): void { - let observableLists: any[] = []; - if (this.selectedRow.length) { - if (this.ISADMNISTRATOR === 'USER.ENABLE_ADMIN_ACTION') { - for (let i = 0; i < this.selectedRow.length; i++) { - // Double confirm user is existing - if (this.selectedRow[i].user_id === 0 || this.isMySelf(this.selectedRow[i].user_id)) { - continue; - } - let updatedUser: User = new User(); - updatedUser.user_id = this.selectedRow[i].user_id; - - updatedUser.has_admin_role = true; // Set as admin - observableLists.push(this.userService.updateUserRole(updatedUser)); + if (usersRole.length && usersRole.every(num => num === 0)) { + this.ISADMNISTRATOR = 'USER.ENABLE_ADMIN_ACTION'; + return true; } - } - if (this.ISADMNISTRATOR === 'USER.DISABLE_ADMIN_ACTION') { - for (let i = 0; i < this.selectedRow.length; i++) { - // Double confirm user is existing - if (this.selectedRow[i].user_id === 0 || this.isMySelf(this.selectedRow[i].user_id)) { - continue; - } - let updatedUser: User = new User(); - updatedUser.user_id = this.selectedRow[i].user_id; - - updatedUser.has_admin_role = false; // Set as none admin - observableLists.push(this.userService.updateUserRole(updatedUser)); + if (usersRole.length && usersRole.every(num => num === 1)) { + this.ISADMNISTRATOR = 'USER.DISABLE_ADMIN_ACTION'; + return true; } - } - - forkJoin(...observableLists).subscribe(() => { - this.selectedRow = []; - this.refresh(); - }, error => { - this.selectedRow = []; - this.msgHandler.handleError(error); - }); - } - } - - // Delete the specified user - deleteUsers(users: User[]): void { - let userArr: string[] = []; - if (this.onlySelf) { - return; + return false; } - if (users && users.length) { - users.forEach(user => { - userArr.push(user.username); - }); + isSystemAdmin(u: User): string { + if (!u) { + return "{{MISS}}"; + } + let key: string = u.has_admin_role ? "USER.IS_ADMIN" : "USER.IS_NOT_ADMIN"; + this.translate.get(key).subscribe((res: string) => this.adminColumn = res); + return this.adminColumn; } - // Confirm deletion - let msg: ConfirmationMessage = new ConfirmationMessage( - "USER.DELETION_TITLE", - "USER.DELETION_SUMMARY", - userArr.join(','), - users, - ConfirmationTargets.USER, - ConfirmationButtons.DELETE_CANCEL - ); - this.deletionDialogService.openComfirmDialog(msg); - } - delUser(users: User[]): void { - let observableLists: any[] = []; - if (users && users.length) { - users.forEach(user => { - observableLists.push(this.delOperate(user)); - }); + adminActions(u: User): string { + if (!u) { + return "{{MISS}}"; + } + let key: string = u.has_admin_role ? "USER.DISABLE_ADMIN_ACTION" : "USER.ENABLE_ADMIN_ACTION"; + this.translate.get(key).subscribe((res: string) => this.adminMenuText = res); + return this.adminMenuText; + } - forkJoin(...observableLists).subscribe((item) => { + public get inProgress(): boolean { + return this.onGoing; + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + if (this.deletionSubscription) { + this.deletionSubscription.unsubscribe(); + } + + if (this.timerHandler) { + clearInterval(this.timerHandler); + this.timerHandler = null; + } + } + + openChangePwdModal(): void { + if (this.selectedRow.length === 1) { + this.changePwdDialog.open(this.selectedRow[0].user_id); + } + } + + // Filter items by keywords + doFilter(terms: string): void { this.selectedRow = []; + this.currentTerm = terms.trim(); + this.currentPage = 1; + this.onGoing = true; + this.getUserListByPaging(); + } + + // Disable the admin role for the specified user + changeAdminRole(): void { + let observableLists: any[] = []; + if (this.selectedRow.length) { + if (this.ISADMNISTRATOR === 'USER.ENABLE_ADMIN_ACTION') { + for (let i = 0; i < this.selectedRow.length; i++) { + // Double confirm user is existing + if (this.selectedRow[i].user_id === 0 || this.isMySelf(this.selectedRow[i].user_id)) { + continue; + } + let updatedUser: User = new User(); + updatedUser.user_id = this.selectedRow[i].user_id; + + updatedUser.has_admin_role = true; // Set as admin + observableLists.push(this.userService.updateUserRole(updatedUser)); + } + } + if (this.ISADMNISTRATOR === 'USER.DISABLE_ADMIN_ACTION') { + for (let i = 0; i < this.selectedRow.length; i++) { + // Double confirm user is existing + if (this.selectedRow[i].user_id === 0 || this.isMySelf(this.selectedRow[i].user_id)) { + continue; + } + let updatedUser: User = new User(); + updatedUser.user_id = this.selectedRow[i].user_id; + + updatedUser.has_admin_role = false; // Set as none admin + observableLists.push(this.userService.updateUserRole(updatedUser)); + } + } + + forkJoin(...observableLists).subscribe(() => { + this.selectedRow = []; + this.refresh(); + }, error => { + this.selectedRow = []; + this.msgHandler.handleError(error); + }); + } + } + + // Delete the specified user + deleteUsers(users: User[]): void { + let userArr: string[] = []; + if (this.onlySelf) { + return; + } + + if (users && users.length) { + users.forEach(user => { + userArr.push(user.username); + }); + } + // Confirm deletion + let msg: ConfirmationMessage = new ConfirmationMessage( + "USER.DELETION_TITLE", + "USER.DELETION_SUMMARY", + userArr.join(','), + users, + ConfirmationTargets.USER, + ConfirmationButtons.DELETE_CANCEL + ); + this.deletionDialogService.openComfirmDialog(msg); + } + + delUser(users: User[]): void { + let observableLists: any[] = []; + if (users && users.length) { + users.forEach(user => { + observableLists.push(this.delOperate(user)); + }); + + forkJoin(...observableLists).subscribe((item) => { + this.selectedRow = []; + this.currentTerm = ''; + this.refresh(); + }); + } + } + + delOperate(user: User): Observable { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_USER'; + operMessage.data.id = user.user_id; + operMessage.state = OperationState.progressing; + operMessage.data.name = user.username; + this.operationService.publishInfo(operMessage); + + if (this.isMySelf(user.user_id)) { + return this.translate.get('BATCH.DELETED_FAILURE').pipe(map(res => { + operateChanges(operMessage, OperationState.failure, res); + })); + } + + return this.userService.deleteUser(user.user_id).pipe(map(() => { + this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => { + operateChanges(operMessage, OperationState.success); + }); + }), catchError(error => { + const message = errorHandFn(error); + this.translate.get(message).subscribe(res => + operateChanges(operMessage, OperationState.failure, res) + ); + return observableThrowError(message); + })); + } + + // Refresh the user list + refreshUser(): void { + this.selectedRow = []; + // Start to get this.currentTerm = ''; + this.onGoing = true; + this.getUserListByPaging(); + } + + // Add new user + addNewUser(): void { + if (!this.canCreateUser) { + return; // No response to this hacking action + } + this.newUserDialog.open(); + } + + // Add user to the user list + addUserToList(user: User): void { + // Currently we can only add it by reloading all this.refresh(); - }); - } - } - - delOperate(user: User): Observable { - // init operation info - let operMessage = new OperateInfo(); - operMessage.name = 'OPERATION.DELETE_USER'; - operMessage.data.id = user.user_id; - operMessage.state = OperationState.progressing; - operMessage.data.name = user.username; - this.operationService.publishInfo(operMessage); - - if (this.isMySelf(user.user_id)) { - return this.translate.get('BATCH.DELETED_FAILURE').pipe(map(res => { - operateChanges(operMessage, OperationState.failure, res); - })); } - return this.userService.deleteUser(user.user_id).pipe(map(() => { - this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => { - operateChanges(operMessage, OperationState.success); - }); - }), catchError(error => { - const message = errorHandFn(error); - this.translate.get(message).subscribe(res => - operateChanges(operMessage, OperationState.failure, res) - ); - return observableThrowError(message); - })); - } - - // Refresh the user list - refreshUser(from: number, to: number): void { - this.selectedRow = []; - // Start to get - this.currentTerm = ''; - this.onGoing = true; - - this.originalUsers = this.userService.getUsers(); - this.originalUsers.subscribe(users => { - this.onGoing = false; - - this.totalCount = users.length; - this.users = users.slice(from, to); // First page - - this.forceRefreshView(5000); - - return users; - }, error => { - this.onGoing = false; - this.msgHandler.handleError(error); - this.forceRefreshView(5000); - }); - } - - // Add new user - addNewUser(): void { - if (!this.canCreateUser) { - return; // No response to this hacking action + // Data loading + load(state: any): void { + this.selectedRow = []; + this.onGoing = true; + this.getUserListByPaging(); } - this.newUserDialog.open(); - } - // Add user to the user list - addUserToList(user: User): void { - // Currently we can only add it by reloading all - this.refresh(); - } - - // Data loading - load(state: any): void { - this.selectedRow = []; - if (state && state.page) { - if (this.originalUsers) { - this.originalUsers.subscribe(users => { - this.users = users.slice(state.page.from, state.page.to + 1); - }); - this.forceRefreshView(5000); - } else { - this.refreshUser(state.page.from, state.page.to + 1); - } - } else { - // Refresh - this.refresh(); + refresh(): void { + this.currentPage = 1; // Refresh pagination + this.refreshUser(); } - } - refresh(): void { - this.currentPage = 1; // Refresh pagination - this.refreshUser(0, 15); - } - - SelectedChange(): void { - this.forceRefreshView(5000); - } - - forceRefreshView(duration: number): void { - // Reset timer - if (this.timerHandler) { - clearInterval(this.timerHandler); + getUserListByPaging() { + this.userService.getUserListByPaging(this.currentPage, this.pageSize, this.currentTerm) + .subscribe(response => { + // Get total count + if (response.headers) { + let xHeader: string = response.headers.get("X-Total-Count"); + if (xHeader) { + this.totalCount = parseInt(xHeader, 0); + } + } + this.users = response.body as User[]; + this.onGoing = false; + }, error => { + this.msgHandler.handleError(error); + this.onGoing = false; + }); } - this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => { - if (this.timerHandler) { - clearInterval(this.timerHandler); - this.timerHandler = null; - } - }, duration); - } - - private isMatchFilterTerm(terms: string, testedItem: string): boolean { - return testedItem.toLowerCase().indexOf(terms.toLowerCase()) !== -1; - } - } diff --git a/src/portal/src/app/user/user.service.ts b/src/portal/src/app/user/user.service.ts index 27efe88f7..717f1ea3d 100644 --- a/src/portal/src/app/user/user.service.ts +++ b/src/portal/src/app/user/user.service.ts @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { map, catchError } from "rxjs/operators"; import { Observable, throwError as observableThrowError } from "rxjs"; -import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "@harbor/ui"; +import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS, buildHttpRequestOptionsWithObserveResponse} from "@harbor/ui"; import { User, LDAPUser } from './user'; import LDAPUsertoUser from './user'; + const userMgmtEndpoint = '/api/users'; const userListSearch = '/api/users/search?'; const ldapUserEndpoint = '/api/ldap/users'; @@ -34,7 +35,19 @@ const ldapUserEndpoint = '/api/ldap/users'; export class UserService { constructor(private http: HttpClient) { } - + // Get paging user list + getUserListByPaging(page: number, pageSize: number, username?: string) { + let params = new HttpParams(); + if (page && pageSize) { + params = params.set('page', page + '').set('page_size', pageSize + ''); + } + if (username) { + params = params.set('username', username); + } + return this.http + .get>(userMgmtEndpoint, buildHttpRequestOptionsWithObserveResponse(params)).pipe( + catchError(error => observableThrowError(error)), ); + } // Handle the related exceptions handleError(error: any): Observable { return observableThrowError(error.error || error);