mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
fix issue #1694: add general message handler to handler the error and error messages
This commit is contained in:
parent
3a3b1c5ad5
commit
3c05a35303
@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Message } from './message';
|
||||
@ -12,7 +12,7 @@ import { AlertType, dismissInterval, httpStatusCode, CommonRoutes } from '../sha
|
||||
selector: 'global-message',
|
||||
templateUrl: 'message.component.html'
|
||||
})
|
||||
export class MessageComponent implements OnInit {
|
||||
export class MessageComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() isAppLevel: boolean;
|
||||
globalMessage: Message = new Message();
|
||||
@ -20,6 +20,10 @@ export class MessageComponent implements OnInit {
|
||||
messageText: string = "";
|
||||
private timer: any = null;
|
||||
|
||||
private appLevelMsgSub: Subscription;
|
||||
private msgSub: Subscription;
|
||||
private clearSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
@ -28,7 +32,7 @@ export class MessageComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
//Only subscribe application level message
|
||||
if (this.isAppLevel) {
|
||||
this.messageService.appLevelAnnounced$.subscribe(
|
||||
this.appLevelMsgSub = this.messageService.appLevelAnnounced$.subscribe(
|
||||
message => {
|
||||
this.globalMessageOpened = true;
|
||||
this.globalMessage = message;
|
||||
@ -39,7 +43,7 @@ export class MessageComponent implements OnInit {
|
||||
)
|
||||
} else {
|
||||
//Only subscribe general messages
|
||||
this.messageService.messageAnnounced$.subscribe(
|
||||
this.msgSub = this.messageService.messageAnnounced$.subscribe(
|
||||
message => {
|
||||
this.globalMessageOpened = true;
|
||||
this.globalMessage = message;
|
||||
@ -53,6 +57,24 @@ export class MessageComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.clearSub = this.messageService.clearChan$.subscribe(clear => {
|
||||
this.onClose();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.appLevelMsgSub) {
|
||||
this.appLevelMsgSub.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.msgSub) {
|
||||
this.msgSub.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.clearSub) {
|
||||
this.clearSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
//Translate or refactor the message shown to user
|
||||
@ -65,20 +87,12 @@ export class MessageComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
//Override key for HTTP 401 and 403
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||
key = "UNAUTHORIZED_ERROR";
|
||||
} else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
key = "FORBIDDEN_ERROR";
|
||||
}
|
||||
|
||||
this.translate.get(key, { 'param': param }).subscribe((res: string) => this.messageText = res);
|
||||
}
|
||||
|
||||
public get needAuth(): boolean {
|
||||
return this.globalMessage ?
|
||||
(this.globalMessage.statusCode === httpStatusCode.Unauthorized) ||
|
||||
(this.globalMessage.statusCode === httpStatusCode.Forbidden) : false;
|
||||
this.globalMessage.statusCode === httpStatusCode.Unauthorized : false;
|
||||
}
|
||||
|
||||
//Show message text
|
||||
|
@ -8,9 +8,11 @@ export class MessageService {
|
||||
|
||||
private messageAnnouncedSource = new Subject<Message>();
|
||||
private appLevelAnnouncedSource = new Subject<Message>();
|
||||
private clearSource = new Subject<boolean>();
|
||||
|
||||
messageAnnounced$ = this.messageAnnouncedSource.asObservable();
|
||||
appLevelAnnounced$ = this.appLevelAnnouncedSource.asObservable();
|
||||
clearChan$ = this.clearSource.asObservable();
|
||||
|
||||
announceMessage(statusCode: number, message: string, alertType: AlertType) {
|
||||
this.messageAnnouncedSource.next(Message.newMessage(statusCode, message, alertType));
|
||||
@ -19,4 +21,8 @@ export class MessageService {
|
||||
announceAppLevelMessage(statusCode: number, message: string, alertType: AlertType) {
|
||||
this.appLevelAnnouncedSource.next(Message.newMessage(statusCode, message, alertType));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.clearSource.next(true);
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
{ path: 'password-reset', component: ResetPasswordComponent },
|
||||
{ path: 'reset_password', component: ResetPasswordComponent },
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
|
@ -0,0 +1,78 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
import { AlertType, httpStatusCode } from '../../shared/shared.const';
|
||||
|
||||
@Injectable()
|
||||
export class MessageHandlerService {
|
||||
|
||||
constructor(private msgService: MessageService) { }
|
||||
|
||||
//Handle the error and map it to the suitable message
|
||||
//base on the status code of error.
|
||||
|
||||
public handleError(error: any | string): void {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!error.statusCode) {
|
||||
//treat as string message
|
||||
let msg = "" + error;
|
||||
this.msgService.announceMessage(500, msg, AlertType.DANGER);
|
||||
} else {
|
||||
let msg = 'UNKNOWN_ERROR';
|
||||
switch (error.statusCode) {
|
||||
case 400:
|
||||
msg = "BAD_REQUEST_ERROR";
|
||||
break;
|
||||
case 401:
|
||||
msg = "UNAUTHORIZED_ERROR";
|
||||
this.msgService.announceAppLevelMessage(error.statusCode, msg, AlertType.DANGER);
|
||||
return;
|
||||
case 403:
|
||||
msg = "FORBIDDEN_ERROR";
|
||||
break;
|
||||
case 404:
|
||||
msg = "NOT_FOUND_ERROR";
|
||||
break;
|
||||
case 409:
|
||||
msg = "CONFLICT_ERROR";
|
||||
break;
|
||||
case 500:
|
||||
msg = "SERVER_ERROR";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.msgService.announceMessage(error.statusCode, msg, AlertType.DANGER);
|
||||
}
|
||||
}
|
||||
|
||||
public showSuccess(message: string): void {
|
||||
if (message && message.trim() != "") {
|
||||
this.msgService.announceMessage(200, message, AlertType.SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
public showInfo(message: string): void {
|
||||
if (message && message.trim() != "") {
|
||||
this.msgService.announceMessage(200, message, AlertType.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
public showWarning(message: string): void {
|
||||
if (message && message.trim() != "") {
|
||||
this.msgService.announceMessage(400, message, AlertType.WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.msgService.clear();
|
||||
}
|
||||
|
||||
public isAppLevel(error): boolean {
|
||||
return error && error.statusCode === httpStatusCode.Unauthorized;
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@
|
||||
.status-code {
|
||||
font-weight: bolder;
|
||||
font-size: 4em;
|
||||
color: #A32100;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,15 @@ import { SessionService } from '../../shared/session.service';
|
||||
import { CommonRoutes, AdmiralQueryParamKey } from '../../shared/shared.const';
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
import { maintainUrlQueryParmas } from '../../shared/shared.utils';
|
||||
import { MessageHandlerService } from '../message-handler/message-handler.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
||||
constructor(
|
||||
private authService: SessionService,
|
||||
private router: Router,
|
||||
private appConfigService: AppConfigService) { }
|
||||
private appConfigService: AppConfigService,
|
||||
private msgHandler: MessageHandlerService) { }
|
||||
|
||||
private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||
const proRegExp = /\/harbor\/projects\/[\d]+\/.+/i;
|
||||
@ -29,6 +31,9 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
//When routing change, clear
|
||||
this.msgHandler.clear();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
//Before activating, we firstly need to confirm whether the route is coming from peer part - admiral
|
||||
let queryParams = route.queryParams;
|
||||
|
@ -37,6 +37,8 @@ import { MemberGuard } from './route/member-guard-activate.service';
|
||||
import { ListProjectROComponent } from './list-project-ro/list-project-ro.component';
|
||||
import { ListRepositoryROComponent } from './list-repository-ro/list-repository-ro.component';
|
||||
|
||||
import { MessageHandlerService } from './message-handler/message-handler.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
@ -88,7 +90,8 @@ import { ListRepositoryROComponent } from './list-repository-ro/list-repository-
|
||||
AuthCheckGuard,
|
||||
SignInGuard,
|
||||
LeavingConfigRouteDeactivate,
|
||||
MemberGuard
|
||||
MemberGuard,
|
||||
MessageHandlerService
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
@ -36,6 +36,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic-item-divider"></div>
|
||||
<div class="statistic-block">Storage</div>
|
||||
<div class="statistic-block">
|
||||
<div>{{freeStorage}}GB | {{totalStorage}}GB</div>
|
||||
<div>[STORAGE]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -9,6 +9,7 @@ import { MessageService } from '../../global-message/message.service';
|
||||
import { Statistics } from './statistics';
|
||||
|
||||
import { SessionService } from '../session.service';
|
||||
import { Volumes } from './volumes';
|
||||
|
||||
@Component({
|
||||
selector: 'statistics-panel',
|
||||
@ -20,6 +21,7 @@ import { SessionService } from '../session.service';
|
||||
export class StatisticsPanelComponent implements OnInit {
|
||||
|
||||
private originalCopy: Statistics = new Statistics();
|
||||
private volumesInfo: Volumes = new Volumes();
|
||||
|
||||
constructor(
|
||||
private statistics: StatisticsService,
|
||||
@ -27,23 +29,46 @@ export class StatisticsPanelComponent implements OnInit {
|
||||
private session: SessionService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.session.getCurrentUser()) {
|
||||
if (this.isValidSession) {
|
||||
this.getStatistics();
|
||||
this.getVolumes();
|
||||
}
|
||||
}
|
||||
|
||||
getStatistics(): void {
|
||||
public get totalStorage(): number {
|
||||
return this.getGBFromBytes(this.volumesInfo.storage.total);
|
||||
}
|
||||
|
||||
public get freeStorage(): number {
|
||||
return this.getGBFromBytes(this.volumesInfo.storage.free);
|
||||
}
|
||||
|
||||
public getStatistics(): void {
|
||||
this.statistics.getStatistics()
|
||||
.then(statistics => this.originalCopy = statistics)
|
||||
.catch(error => {
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.WARNING);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public getVolumes(): void {
|
||||
this.statistics.getVolumes()
|
||||
.then(volumes => this.volumesInfo = volumes)
|
||||
.catch(error => {
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(error.status, errorHandler(error), AlertType.WARNING);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get isValidSession(): boolean {
|
||||
let user = this.session.getCurrentUser();
|
||||
return user && user.has_admin_role > 0;
|
||||
}
|
||||
|
||||
private getGBFromBytes(bytes: number): number {
|
||||
return Math.round((bytes / (1024 * 1024 * 1024)));
|
||||
}
|
||||
}
|
@ -3,8 +3,10 @@ import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { Statistics } from './statistics';
|
||||
import { Volumes } from './volumes';
|
||||
|
||||
export const statisticsEndpoint = "/api/statistics";
|
||||
const statisticsEndpoint = "/api/statistics";
|
||||
const volumesEndpoint = "/api/systeminfo/volumes";
|
||||
/**
|
||||
* Declare service to handle the top repositories
|
||||
*
|
||||
@ -28,4 +30,10 @@ export class StatisticsService {
|
||||
.then(response => response.json() as Statistics)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
getVolumes(): Promise<Volumes> {
|
||||
return this.http.get(volumesEndpoint, this.options).toPromise()
|
||||
.then(response => response.json() as Volumes)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
17
src/ui_ng/src/app/shared/statictics/volumes.ts
Normal file
17
src/ui_ng/src/app/shared/statictics/volumes.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export class Volumes {
|
||||
constructor(){
|
||||
this.storage = new Storage();
|
||||
}
|
||||
|
||||
storage: Storage;
|
||||
}
|
||||
|
||||
export class Storage {
|
||||
constructor(){
|
||||
this.total = 0;
|
||||
this.free = 0;
|
||||
}
|
||||
|
||||
total: number;
|
||||
free: number;
|
||||
}
|
@ -6,10 +6,8 @@ import { User } from './user';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { UserService } from './user.service';
|
||||
import { errorHandler, accessErrorHandler } from '../shared/shared.utils';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { AlertType, httpStatusCode } from '../shared/shared.const';
|
||||
import { InlineAlertComponent } from '../shared/inline-alert/inline-alert.component';
|
||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
||||
|
||||
@Component({
|
||||
selector: "new-user-modal",
|
||||
@ -26,7 +24,7 @@ export class NewUserModalComponent {
|
||||
|
||||
constructor(private session: SessionService,
|
||||
private userService: UserService,
|
||||
private msgService: MessageService) { }
|
||||
private msgHandler: MessageHandlerService) { }
|
||||
|
||||
@ViewChild(NewUserFormComponent)
|
||||
private newUserForm: NewUserFormComponent;
|
||||
@ -45,10 +43,6 @@ export class NewUserModalComponent {
|
||||
return this.newUserForm.isValid && this.error == null;
|
||||
}
|
||||
|
||||
public get errorMessage(): string {
|
||||
return errorHandler(this.error);
|
||||
}
|
||||
|
||||
formValueChange(flag: boolean): void {
|
||||
if (this.error != null) {
|
||||
this.error = null;//clear error
|
||||
@ -114,12 +108,13 @@ export class NewUserModalComponent {
|
||||
|
||||
this.addNew.emit(u);
|
||||
this.opened = false;
|
||||
this.msgService.announceMessage(200, "USER.SAVE_SUCCESS", AlertType.SUCCESS);
|
||||
this.msgHandler.showSuccess("USER.SAVE_SUCCESS");
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
this.error = error;
|
||||
if(accessErrorHandler(error, this.msgService)){
|
||||
if(this.msgHandler.isAppLevel(error)){
|
||||
this.msgHandler.handleError(error);
|
||||
this.opened = false;
|
||||
}else{
|
||||
this.inlineAlert.showInlineError(error);
|
||||
|
@ -8,9 +8,8 @@ import { NewUserModalComponent } from './new-user-modal.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationState, ConfirmationTargets, AlertType, httpStatusCode } from '../shared/shared.const'
|
||||
import { errorHandler, accessErrorHandler } from '../shared/shared.utils';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const'
|
||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
@ -37,7 +36,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
private userService: UserService,
|
||||
private translate: TranslateService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private msgService: MessageService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService) {
|
||||
this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => {
|
||||
if (confirmed &&
|
||||
@ -50,8 +49,8 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
|
||||
private isMySelf(uid: number): boolean {
|
||||
let currentUser = this.session.getCurrentUser();
|
||||
if(currentUser){
|
||||
if(currentUser.user_id === uid ){
|
||||
if (currentUser) {
|
||||
if (currentUser.user_id === uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -115,7 +114,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.isMySelf(user.user_id)){
|
||||
if (this.isMySelf(user.user_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -136,9 +135,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
user.has_admin_role = updatedUser.has_admin_role;
|
||||
})
|
||||
.catch(error => {
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(500, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
this.msgHandler.handleError(error);
|
||||
})
|
||||
}
|
||||
|
||||
@ -148,7 +145,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.isMySelf(user.user_id)){
|
||||
if (this.isMySelf(user.user_id)) {
|
||||
return; //Double confirm
|
||||
}
|
||||
|
||||
@ -170,13 +167,11 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
//and then view refreshed
|
||||
this.originalUsers.then(users => {
|
||||
this.users = users.filter(u => u.user_id != user.user_id);
|
||||
this.msgService.announceMessage(500, "USER.DELETE_SUCCESS", AlertType.SUCCESS);
|
||||
this.msgHandler.showSuccess("USER.DELETE_SUCCESS");
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(500, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -194,9 +189,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
if (!accessErrorHandler(error, this.msgService)) {
|
||||
this.msgService.announceMessage(500, errorHandler(error), AlertType.DANGER);
|
||||
}
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -413,7 +413,11 @@
|
||||
"BACK": "Back"
|
||||
},
|
||||
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later",
|
||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue the operation",
|
||||
"FORBIDDEN_ERROR": "You are not allowed to perform this operation",
|
||||
"GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}"
|
||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action",
|
||||
"FORBIDDEN_ERROR": "You do not have the proper privileges to perform the action",
|
||||
"GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}",
|
||||
"BAD_REQUEST_ERROR": "We are unable to perform your action because of a bad request",
|
||||
"NOT_FOUND_ERROR": "Your request can not be completed because the object does not exist",
|
||||
"CONFLICT_ERROR": "We are unable to perform your action because your submission has conflicts",
|
||||
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred"
|
||||
}
|
@ -415,5 +415,9 @@
|
||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试",
|
||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续",
|
||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限",
|
||||
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}"
|
||||
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}",
|
||||
"BAD_REQUEST_ERROR": "错误请求导致无法完成操作",
|
||||
"NOT_FOUND_ERROR": "对象不存在故无法完成你的请求",
|
||||
"CONFLICT_ERROR": "你的提交包含冲突故操作无法完成",
|
||||
"SERVER_ERROR": "服务器出现内部错误,请求无法完成"
|
||||
}
|
Loading…
Reference in New Issue
Block a user