From 1121c8a76baa249a33fcd288496bbbe73cb197fe Mon Sep 17 00:00:00 2001 From: pfh Date: Mon, 21 May 2018 19:05:38 +0800 Subject: [PATCH] Add async task progress and delete dialog task progress #4371 norm code --- .../lib/src/_animations/fade-in.animation.ts | 17 + src/ui_ng/lib/src/_animations/index.ts | 2 + .../src/_animations/slide-in-out.animation.ts | 47 + .../confirmation-dialog.component.html | 19 +- .../confirmation-dialog.component.ts | 31 +- .../create-edit-rule.component.spec.ts | 4 +- .../lib/src/endpoint/endpoint.component.html | 2 +- .../src/endpoint/endpoint.component.spec.ts | 4 +- .../lib/src/endpoint/endpoint.component.ts | 381 ++++--- src/ui_ng/lib/src/harbor-library.module.ts | 18 +- src/ui_ng/lib/src/index.ts | 2 + src/ui_ng/lib/src/label/label.component.html | 5 +- .../lib/src/label/label.component.spec.ts | 5 +- src/ui_ng/lib/src/label/label.component.ts | 313 +++--- .../list-replication-rule.component.html | 4 +- .../list-replication-rule.component.spec.ts | 4 +- .../list-replication-rule.component.ts | 459 ++++----- src/ui_ng/lib/src/operation/index.ts | 13 + src/ui_ng/lib/src/operation/operate.ts | 30 + .../lib/src/operation/operation.component.css | 43 + .../src/operation/operation.component.html | 64 ++ .../lib/src/operation/operation.component.ts | 144 +++ .../lib/src/operation/operation.service.ts | 15 + .../replication/replication.component.html | 5 +- .../replication/replication.component.spec.ts | 4 +- .../src/replication/replication.component.ts | 73 +- .../repository-gridview.component.html | 49 +- .../repository-gridview.component.spec.ts | 4 +- .../repository-gridview.component.ts | 947 ++++++++---------- .../repository/repository.component.spec.ts | 2 + src/ui_ng/lib/src/tag/tag.component.html | 2 +- src/ui_ng/lib/src/tag/tag.component.spec.ts | 4 +- src/ui_ng/lib/src/tag/tag.component.ts | 42 +- src/ui_ng/package.json | 2 +- .../harbor-shell/harbor-shell.component.html | 1 + .../list-project/list-project.component.ts | 30 +- .../app/project/member/member.component.html | 3 +- .../app/project/member/member.component.ts | 96 +- .../confirmation-batch-message.ts | 27 - .../confirmation-dialog.component.html | 15 +- .../confirmation-dialog.component.ts | 52 - .../confirmation-dialog.service.ts | 6 - src/ui_ng/src/app/user/user.component.ts | 54 +- src/ui_ng/src/i18n/lang/en-us-lang.json | 21 + src/ui_ng/src/i18n/lang/es-es-lang.json | 21 + src/ui_ng/src/i18n/lang/fr-fr-lang.json | 21 + src/ui_ng/src/i18n/lang/zh-cn-lang.json | 21 + tests/resources/Harbor-Pages/Project.robot | 1 - 48 files changed, 1680 insertions(+), 1449 deletions(-) create mode 100644 src/ui_ng/lib/src/_animations/fade-in.animation.ts create mode 100644 src/ui_ng/lib/src/_animations/index.ts create mode 100644 src/ui_ng/lib/src/_animations/slide-in-out.animation.ts create mode 100644 src/ui_ng/lib/src/operation/index.ts create mode 100644 src/ui_ng/lib/src/operation/operate.ts create mode 100644 src/ui_ng/lib/src/operation/operation.component.css create mode 100644 src/ui_ng/lib/src/operation/operation.component.html create mode 100644 src/ui_ng/lib/src/operation/operation.component.ts create mode 100644 src/ui_ng/lib/src/operation/operation.service.ts delete mode 100644 src/ui_ng/src/app/shared/confirmation-dialog/confirmation-batch-message.ts diff --git a/src/ui_ng/lib/src/_animations/fade-in.animation.ts b/src/ui_ng/lib/src/_animations/fade-in.animation.ts new file mode 100644 index 000000000..6b15d688d --- /dev/null +++ b/src/ui_ng/lib/src/_animations/fade-in.animation.ts @@ -0,0 +1,17 @@ +// import the required animation functions from the angular animations module +import {AnimationTriggerMetadata, trigger, animate, transition, style } from '@angular/animations'; + +export const FadeInAnimation: AnimationTriggerMetadata = + // trigger name for attaching this animation to an element using the [@triggerName] syntax + trigger('FadeInAnimation', [ + + // route 'enter' transition + transition(':enter', [ + + // css styles at start of transition + style({ opacity: 0 }), + + // animation and styles at end of transition + animate('.3s', style({ opacity: 1 })) + ]), + ]); diff --git a/src/ui_ng/lib/src/_animations/index.ts b/src/ui_ng/lib/src/_animations/index.ts new file mode 100644 index 000000000..fe839081c --- /dev/null +++ b/src/ui_ng/lib/src/_animations/index.ts @@ -0,0 +1,2 @@ +export * from './fade-in.animation'; +export * from './slide-in-out.animation'; diff --git a/src/ui_ng/lib/src/_animations/slide-in-out.animation.ts b/src/ui_ng/lib/src/_animations/slide-in-out.animation.ts new file mode 100644 index 000000000..8d8065313 --- /dev/null +++ b/src/ui_ng/lib/src/_animations/slide-in-out.animation.ts @@ -0,0 +1,47 @@ +// import the required animation functions from the angular animations module +import {AnimationTriggerMetadata, trigger, state, animate, transition, style } from '@angular/animations'; + +export const SlideInOutAnimation: AnimationTriggerMetadata = + // trigger name for attaching this animation to an element using the [@triggerName] syntax + trigger('SlideInOutAnimation', [ + + // end state styles for route container (host) + state('in', style({ + // the view covers the whole screen with a semi tranparent background + position: 'fix', + right: 0, + width: '350px', + bottom: 0 + // backgroundColor: 'rgba(0, 0, 0, 0.8)' + })), + state('out', style({ + // the view covers the whole screen with a semi tranparent background + position: 'fix', + width: '30px', + bottom: 0 + // backgroundColor: 'rgba(0, 0, 0, 0.8)' + })), + // route 'enter' transition + transition('out => in', [ + // animation and styles at end of transition + animate('.5s ease-in-out', style({ + // transition the right position to 0 which slides the content into view + width: '350px', + + // transition the background opacity to 0.8 to fade it in + // backgroundColor: 'rgba(0, 0, 0, 0.8)' + })) + ]), + + // route 'leave' transition + transition('in => out', [ + // animation and styles at end of transition + animate('.5s ease-in-out', style({ + // transition the right position to -400% which slides the content out of view + width: '30px', + + // transition the background opacity to 0 to fade it out + // backgroundColor: 'rgba(0, 0, 0, 0)' + })) + ]) + ]); diff --git a/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.html b/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.html index 678f61b91..e1ca78529 100644 --- a/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.html +++ b/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.html @@ -5,19 +5,6 @@
{{dialogContent}}
-
- -
\ No newline at end of file diff --git a/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.ts b/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.ts index 054b3260c..af7dafe44 100644 --- a/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.ts +++ b/src/ui_ng/lib/src/confirmation-dialog/confirmation-dialog.component.ts @@ -11,7 +11,7 @@ // 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, EventEmitter, Input, Output} from '@angular/core'; +import {Component, EventEmitter, Output} from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { ConfirmationMessage } from './confirmation-message'; @@ -35,7 +35,6 @@ export class ConfirmationDialogComponent { @Output() confirmAction = new EventEmitter(); @Output() cancelAction = new EventEmitter(); - @Input() batchInfors: BatchInfo[] = []; isDelete = false; constructor( @@ -52,12 +51,6 @@ export class ConfirmationDialogComponent { this.opened = true; } - get batchOverStatus(): boolean { - if (this.batchInfors.length) { - return this.batchInfors.every(item => item.loading === false); - } - return false; - } colorChange(list: BatchInfo) { if (!list.loading && !list.errorState) { @@ -74,7 +67,6 @@ export class ConfirmationDialogComponent { } close(): void { - this.batchInfors = []; this.opened = false; } @@ -96,27 +88,6 @@ export class ConfirmationDialogComponent { this.close(); } - operate(): void { - if (!this.message) {// Inproper condition - this.close(); - return; - } - - if (this.batchInfors.length) { - this.batchInfors.every(item => item.loading = true); - this.isDelete = true; - } - - let data: any = this.message.data ? this.message.data : {}; - let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY; - let message = new ConfirmationAcknowledgement( - ConfirmationState.CONFIRMED, - data, - target - ); - this.confirmAction.emit(message); - } - confirm(): void { if (!this.message) {// Inproper condition this.close(); diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts index 0569734c0..359247281 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts @@ -37,6 +37,7 @@ import { ProjectService } from "../service/project.service"; import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component"; +import { OperationService } from "../operation/operation.service"; describe("CreateEditRuleComponent (inline template)", () => { let mockRules: ReplicationRule[] = [ @@ -246,7 +247,8 @@ describe("CreateEditRuleComponent (inline template)", () => { { provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService }, { provide: ProjectService, useClass: ProjectDefaultService }, - { provide: JobLogService, useClass: JobLogDefaultService } + { provide: JobLogService, useClass: JobLogDefaultService }, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.html b/src/ui_ng/lib/src/endpoint/endpoint.component.html index f9ed2f8e9..f356ed7a9 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.html +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.html @@ -38,6 +38,6 @@ - + \ No newline at end of file diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts index af5a2979d..3cbe0feca 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts @@ -16,6 +16,7 @@ import { EndpointDefaultService } from "../service/endpoint.service"; import { IServiceConfig, SERVICE_CONFIG } from "../service.config"; +import { OperationService } from "../operation/operation.service"; import { click } from "../utils"; @@ -94,7 +95,8 @@ describe("EndpointComponent (inline template)", () => { providers: [ ErrorHandler, { provide: SERVICE_CONFIG, useValue: config }, - { provide: EndpointService, useClass: EndpointDefaultService } + { provide: EndpointService, useClass: EndpointDefaultService }, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.ts index 25be9fb8b..2bb14899b 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.ts @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. import { - Component, - OnInit, - OnDestroy, - ViewChild, - ChangeDetectionStrategy, - ChangeDetectorRef + Component, + OnInit, + OnDestroy, + ViewChild, + ChangeDetectionStrategy, + ChangeDetectorRef } from "@angular/core"; import { Subscription } from "rxjs/Subscription"; import { Observable } from "rxjs/Observable"; @@ -30,220 +30,211 @@ import { EndpointService } from "../service/endpoint.service"; import { ErrorHandler } from "../error-handler/index"; -import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; -import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; -import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; +import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; +import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; +import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; import { - ConfirmationTargets, - ConfirmationState, - ConfirmationButtons + ConfirmationTargets, + ConfirmationState, + ConfirmationButtons } from "../shared/shared.const"; import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component"; import { toPromise, CustomComparator } from "../utils"; -import { - BatchInfo, - BathInfoChanges -} from "../confirmation-dialog/confirmation-batch-message"; - +import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; +import {OperationService} from "../operation/operation.service"; @Component({ - selector: "hbr-endpoint", - templateUrl: "./endpoint.component.html", - styleUrls: ["./endpoint.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush + selector: "hbr-endpoint", + templateUrl: "./endpoint.component.html", + styleUrls: ["./endpoint.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class EndpointComponent implements OnInit, OnDestroy { - @ViewChild(CreateEditEndpointComponent) - createEditEndpointComponent: CreateEditEndpointComponent; + @ViewChild(CreateEditEndpointComponent) + createEditEndpointComponent: CreateEditEndpointComponent; - @ViewChild("confirmationDialog") - confirmationDialogComponent: ConfirmationDialogComponent; + @ViewChild("confirmationDialog") + confirmationDialogComponent: ConfirmationDialogComponent; - targets: Endpoint[]; - target: Endpoint; + targets: Endpoint[]; + target: Endpoint; - targetName: string; - subscription: Subscription; + targetName: string; + subscription: Subscription; - loading: boolean = false; + loading: boolean = false; - creationTimeComparator: Comparator = new CustomComparator( - "creation_time", - "date" - ); + creationTimeComparator: Comparator = new CustomComparator( + "creation_time", + "date" + ); - timerHandler: any; - selectedRow: Endpoint[] = []; - batchDelectionInfos: BatchInfo[] = []; + timerHandler: any; + selectedRow: Endpoint[] = []; - get initEndpoint(): Endpoint { - return { - endpoint: "", - name: "", - username: "", - password: "", - insecure: false, - type: 0 - }; - } - - constructor( - private endpointService: EndpointService, - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private ref: ChangeDetectorRef - ) { - this.forceRefreshView(1000); - } - - ngOnInit(): void { - this.targetName = ""; - this.retrieve(); - } - - ngOnDestroy(): void { - if (this.subscription) { - this.subscription.unsubscribe(); + get initEndpoint(): Endpoint { + return { + endpoint: "", + name: "", + username: "", + password: "", + insecure: false, + type: 0 + }; } - } - selectedChange(): void { - this.forceRefreshView(5000); - } - retrieve(): void { - this.loading = true; - this.selectedRow = []; - toPromise(this.endpointService.getEndpoints(this.targetName)) - .then(targets => { - this.targets = targets || []; + constructor(private endpointService: EndpointService, + private errorHandler: ErrorHandler, + private translateService: TranslateService, + private operationService: OperationService, + private ref: ChangeDetectorRef) { this.forceRefreshView(1000); - this.loading = false; - }) - .catch(error => { - this.errorHandler.error(error); - this.loading = false; - }); - } - - doSearchTargets(targetName: string) { - this.targetName = targetName; - this.retrieve(); - } - - refreshTargets() { - this.retrieve(); - } - - reload($event: any) { - this.targetName = ""; - this.retrieve(); - } - - openModal() { - this.createEditEndpointComponent.openCreateEditTarget(true); - this.target = this.initEndpoint; - } - - editTargets(targets: Endpoint[]) { - if (targets && targets.length === 1) { - let target = targets[0]; - let editable = true; - if (!target.id) { - return; - } - let id: number | string = target.id; - this.createEditEndpointComponent.openCreateEditTarget(editable, id); } - } - deleteTargets(targets: Endpoint[]) { - if (targets && targets.length) { - let targetNames: string[] = []; - this.batchDelectionInfos = []; - targets.forEach(target => { - targetNames.push(target.name); - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = target.name; - this.batchDelectionInfos.push(initBatchMessage); - }); - let deletionMessage = new ConfirmationMessage( - "REPLICATION.DELETION_TITLE_TARGET", - "REPLICATION.DELETION_SUMMARY_TARGET", - targetNames.join(", ") || "", - targets, - ConfirmationTargets.TARGET, - ConfirmationButtons.DELETE_CANCEL - ); - this.confirmationDialogComponent.open(deletionMessage); + ngOnInit(): void { + this.targetName = ""; + this.retrieve(); } - } - confirmDeletion(message: ConfirmationAcknowledgement) { - if ( - message && - message.source === ConfirmationTargets.TARGET && - message.state === ConfirmationState.CONFIRMED - ) { - let targetLists: Endpoint[] = message.data; - if (targetLists && targetLists.length) { - let promiseLists: any[] = []; - targetLists.forEach(target => { - promiseLists.push(this.delOperate(target.id, target.name)); - }); - Promise.all(promiseLists).then(item => { - this.selectedRow = []; - this.reload(true); - this.forceRefreshView(2000); - }); - } - } - } - delOperate(id: number | string, name: string) { - let findedList = this.batchDelectionInfos.find(data => data.name === name); - return toPromise(this.endpointService.deleteEndpoint(id)) - .then(response => { - this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { - findedList = BathInfoChanges(findedList, res); - }); - }) - .catch(error => { - if (error && error.status === 412) { - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get( - "DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED" - ) - ).subscribe(res => { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - } else { - this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); - }); + ngOnDestroy(): void { + if (this.subscription) { + this.subscription.unsubscribe(); } - }); - } - // Forcely refresh the view - forceRefreshView(duration: number): void { - // Reset timer - if (this.timerHandler) { - clearInterval(this.timerHandler); } - this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => { - if (this.timerHandler) { - clearInterval(this.timerHandler); - this.timerHandler = null; - } - }, duration); - } + + selectedChange(): void { + this.forceRefreshView(5000); + } + + retrieve(): void { + this.loading = true; + this.selectedRow = []; + toPromise(this.endpointService.getEndpoints(this.targetName)) + .then(targets => { + this.targets = targets || []; + this.forceRefreshView(1000); + this.loading = false; + }) + .catch(error => { + this.errorHandler.error(error); + this.loading = false; + }); + } + + doSearchTargets(targetName: string) { + this.targetName = targetName; + this.retrieve(); + } + + refreshTargets() { + this.retrieve(); + } + + reload($event: any) { + this.targetName = ""; + this.retrieve(); + } + + openModal() { + this.createEditEndpointComponent.openCreateEditTarget(true); + this.target = this.initEndpoint; + } + + editTargets(targets: Endpoint[]) { + if (targets && targets.length === 1) { + let target = targets[0]; + let editable = true; + if (!target.id) { + return; + } + let id: number | string = target.id; + this.createEditEndpointComponent.openCreateEditTarget(editable, id); + } + } + + deleteTargets(targets: Endpoint[]) { + if (targets && targets.length) { + let targetNames: string[] = []; + targets.forEach(target => { + targetNames.push(target.name); + }); + let deletionMessage = new ConfirmationMessage( + 'REPLICATION.DELETION_TITLE_TARGET', + 'REPLICATION.DELETION_SUMMARY_TARGET', + targetNames.join(', ') || '', + targets, + ConfirmationTargets.TARGET, + ConfirmationButtons.DELETE_CANCEL); + this.confirmationDialogComponent.open(deletionMessage); + } + } + + confirmDeletion(message: ConfirmationAcknowledgement) { + if (message && + message.source === ConfirmationTargets.TARGET && + message.state === ConfirmationState.CONFIRMED) { + let targetLists: Endpoint[] = message.data; + if (targetLists && targetLists.length) { + let promiseLists: any[] = []; + targetLists.forEach(target => { + promiseLists.push(this.delOperate(target)); + }); + Promise.all(promiseLists).then((item) => { + this.selectedRow = []; + this.reload(true); + this.forceRefreshView(2000); + }); + } + } + } + + delOperate(target: Endpoint) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_REGISTRY'; + operMessage.data.id = target.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = target.name; + this.operationService.publishInfo(operMessage); + + return toPromise(this.endpointService + .deleteEndpoint(target.id)) + .then( + response => { + this.translateService.get('BATCH.DELETED_SUCCESS') + .subscribe(res => { + operateChanges(operMessage, OperationState.success); + }); + }).catch( + error => { + if (error && error.status === 412) { + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + } else { + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + } + }); + } + + // Forcely refresh the view + forceRefreshView(duration: number): void { + // Reset timer + if (this.timerHandler) { + clearInterval(this.timerHandler); + } + this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => { + if (this.timerHandler) { + clearInterval(this.timerHandler); + this.timerHandler = null; + } + }, duration); + } } diff --git a/src/ui_ng/lib/src/harbor-library.module.ts b/src/ui_ng/lib/src/harbor-library.module.ts index 656bc6a85..bf4af244f 100644 --- a/src/ui_ng/lib/src/harbor-library.module.ts +++ b/src/ui_ng/lib/src/harbor-library.module.ts @@ -24,6 +24,10 @@ import { JOB_LOG_VIEWER_DIRECTIVES } from './job-log-viewer/index'; import { PROJECT_POLICY_CONFIG_DIRECTIVES } from './project-policy-config/index'; import { HBR_GRIDVIEW_DIRECTIVES } from './gridview/index'; import { REPOSITORY_GRIDVIEW_DIRECTIVES } from './repository-gridview/index'; +import { OPERATION_DIRECTIVES } from './operation/index'; +import {LABEL_DIRECTIVES} from "./label/index"; +import {CREATE_EDIT_LABEL_DIRECTIVES} from "./create-edit-label/index"; +import {LABEL_PIECE_DIRECTIVES} from "./label-piece/index"; import { SystemInfoService, @@ -47,7 +51,7 @@ import { ProjectService, ProjectDefaultService, LabelService, - LabelDefaultService + LabelDefaultService, } from './service/index'; import { ErrorHandler, @@ -59,9 +63,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { TranslateServiceInitializer } from './i18n/index'; import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils'; import { ChannelService } from './channel/index'; -import {LABEL_DIRECTIVES} from "./label/index"; -import {CREATE_EDIT_LABEL_DIRECTIVES} from "./create-edit-label/index"; -import {LABEL_PIECE_DIRECTIVES} from "./label-piece/index"; +import { OperationService } from './operation/operation.service'; /** * Declare default service configuration; all the endpoints will be defined in @@ -182,6 +184,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co LABEL_PIECE_DIRECTIVES, HBR_GRIDVIEW_DIRECTIVES, REPOSITORY_GRIDVIEW_DIRECTIVES, + OPERATION_DIRECTIVES ], exports: [ LOG_DIRECTIVES, @@ -207,6 +210,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co LABEL_PIECE_DIRECTIVES, HBR_GRIDVIEW_DIRECTIVES, REPOSITORY_GRIDVIEW_DIRECTIVES, + OPERATION_DIRECTIVES ], providers: [] }) @@ -237,7 +241,8 @@ export class HarborLibraryModule { deps: [TranslateServiceInitializer, SERVICE_CONFIG], multi: true }, - ChannelService + ChannelService, + OperationService ] }; } @@ -259,7 +264,8 @@ export class HarborLibraryModule { config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService }, config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService }, config.labelService || {provide: LabelService, useClass: LabelDefaultService}, - ChannelService + ChannelService, + OperationService ] }; } diff --git a/src/ui_ng/lib/src/index.ts b/src/ui_ng/lib/src/index.ts index 00d6cfd83..9c25f7477 100644 --- a/src/ui_ng/lib/src/index.ts +++ b/src/ui_ng/lib/src/index.ts @@ -24,3 +24,5 @@ export * from './label/index'; export * from './create-edit-label/index'; export * from './gridview/index'; export * from './repository-gridview/index'; +export * from './operation/index'; +export * from './_animations/index'; diff --git a/src/ui_ng/lib/src/label/label.component.html b/src/ui_ng/lib/src/label/label.component.html index 6cc22c1ca..110d2d15d 100644 --- a/src/ui_ng/lib/src/label/label.component.html +++ b/src/ui_ng/lib/src/label/label.component.html @@ -37,5 +37,6 @@ - - \ No newline at end of file + + + diff --git a/src/ui_ng/lib/src/label/label.component.spec.ts b/src/ui_ng/lib/src/label/label.component.spec.ts index 896e62d16..c78fe83e5 100644 --- a/src/ui_ng/lib/src/label/label.component.spec.ts +++ b/src/ui_ng/lib/src/label/label.component.spec.ts @@ -12,7 +12,7 @@ import {InlineAlertComponent} from "../inline-alert/inline-alert.component"; import {ErrorHandler} from "../error-handler/error-handler"; import {IServiceConfig, SERVICE_CONFIG} from "../service.config"; - +import { OperationService } from "../operation/operation.service"; describe('LabelComponent (inline template)', () => { @@ -79,7 +79,8 @@ describe('LabelComponent (inline template)', () => { providers: [ ErrorHandler, { provide: SERVICE_CONFIG, useValue: config }, - {provide: LabelService, useClass: LabelDefaultService} + {provide: LabelService, useClass: LabelDefaultService}, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/label/label.component.ts b/src/ui_ng/lib/src/label/label.component.ts index 97df5c9ee..2763f5182 100644 --- a/src/ui_ng/lib/src/label/label.component.ts +++ b/src/ui_ng/lib/src/label/label.component.ts @@ -12,174 +12,175 @@ // See the License for the specific language governing permissions and // limitations under the License. import { - Component, - OnInit, - ViewChild, - ChangeDetectionStrategy, - ChangeDetectorRef, - Input + Component, + OnInit, + ViewChild, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input } from "@angular/core"; -import { Label } from "../service/interface"; -import { LabelService } from "../service/label.service"; -import { toPromise } from "../utils"; -import { ErrorHandler } from "../error-handler/error-handler"; -import { CreateEditLabelComponent } from "../create-edit-label/create-edit-label.component"; +import {Label} from "../service/interface"; +import {LabelService} from "../service/label.service"; +import {toPromise} from "../utils"; +import {ErrorHandler} from "../error-handler/error-handler"; +import {CreateEditLabelComponent} from "../create-edit-label/create-edit-label.component"; +import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; import { - BatchInfo, - BathInfoChanges -} from "../confirmation-dialog/confirmation-batch-message"; -import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; -import { - ConfirmationButtons, - ConfirmationState, - ConfirmationTargets + ConfirmationButtons, + ConfirmationState, + ConfirmationTargets } from "../shared/shared.const"; -import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; -import { TranslateService } from "@ngx-translate/core"; -import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; +import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; +import {TranslateService} from "@ngx-translate/core"; +import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; +import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; +import {OperationService} from "../operation/operation.service"; + @Component({ - selector: "hbr-label", - templateUrl: "./label.component.html", - styleUrls: ["./label.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush + selector: "hbr-label", + templateUrl: "./label.component.html", + styleUrls: ["./label.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class LabelComponent implements OnInit { - timerHandler: any; - loading: boolean; - targets: Label[]; - targetName: string; + timerHandler: any; + loading: boolean; + targets: Label[]; + targetName: string; + selectedRow: Label[] = []; - selectedRow: Label[] = []; - batchDelectionInfos: BatchInfo[] = []; + @Input() scope: string; + @Input() projectId = 0; + @Input() hasProjectAdminRole: boolean; - @Input() scope: string; - @Input() projectId = 0; - @Input() hasProjectAdminRole: boolean; + @ViewChild(CreateEditLabelComponent) + createEditLabel: CreateEditLabelComponent; + @ViewChild("confirmationDialog") + confirmationDialogComponent: ConfirmationDialogComponent; - @ViewChild(CreateEditLabelComponent) - createEditLabel: CreateEditLabelComponent; - @ViewChild("confirmationDialog") - confirmationDialogComponent: ConfirmationDialogComponent; - constructor( - private labelService: LabelService, - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private ref: ChangeDetectorRef - ) {} - - ngOnInit(): void { - this.retrieve(this.scope); - } - - retrieve(scope: string, name = "") { - this.loading = true; - this.selectedRow = []; - this.targetName = ""; - toPromise(this.labelService.getLabels(scope, this.projectId, name)) - .then(targets => { - this.targets = targets || []; - this.loading = false; - this.forceRefreshView(2000); - }) - .catch(error => { - this.errorHandler.error(error); - this.loading = false; - }); - } - - openModal(): void { - this.createEditLabel.openModal(); - } - - reload(): void { - this.retrieve(this.scope); - } - - doSearchTargets(targetName: string) { - this.retrieve(this.scope, targetName); - } - - refreshTargets() { - this.retrieve(this.scope); - } - - selectedChange(): void { - // this.forceRefreshView(5000); - } - - editLabel(label: Label[]): void { - this.createEditLabel.editModel(label[0].id, label); - } - - deleteLabels(targets: Label[]): void { - if (targets && targets.length) { - let targetNames: string[] = []; - this.batchDelectionInfos = []; - targets.forEach(target => { - targetNames.push(target.name); - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = target.name; - this.batchDelectionInfos.push(initBatchMessage); - }); - let deletionMessage = new ConfirmationMessage( - "LABEL.DELETION_TITLE_TARGET", - "LABEL.DELETION_SUMMARY_TARGET", - targetNames.join(", ") || "", - targets, - ConfirmationTargets.TARGET, - ConfirmationButtons.DELETE_CANCEL - ); - this.confirmationDialogComponent.open(deletionMessage); + constructor(private labelService: LabelService, + private errorHandler: ErrorHandler, + private translateService: TranslateService, + private operationService: OperationService, + private ref: ChangeDetectorRef) { } - } - confirmDeletion(message: ConfirmationAcknowledgement) { - if ( - message && - message.source === ConfirmationTargets.TARGET && - message.state === ConfirmationState.CONFIRMED - ) { - let targetLists: Label[] = message.data; - if (targetLists && targetLists.length) { - let promiseLists: any[] = []; - targetLists.forEach(target => { - promiseLists.push(this.delOperate(target.id, target.name)); - }); - Promise.all(promiseLists).then(item => { - this.selectedRow = []; - this.retrieve(this.scope); - }); - } + ngOnInit(): void { + this.retrieve(this.scope); } - } - delOperate(id: number, name: string) { - let findedList = this.batchDelectionInfos.find(data => data.name === name); - return toPromise(this.labelService.deleteLabel(id)) - .then(response => { - this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { - findedList = BathInfoChanges(findedList, res); - }); - }) - .catch(error => { - this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); - }); - }); - } - - // Forcely refresh the view - forceRefreshView(duration: number): void { - // Reset timer - if (this.timerHandler) { - clearInterval(this.timerHandler); + retrieve(scope: string, name = "") { + this.loading = true; + this.selectedRow = []; + this.targetName = ""; + toPromise(this.labelService.getLabels(scope, this.projectId, name)) + .then(targets => { + this.targets = targets || []; + this.loading = false; + this.forceRefreshView(2000); + }) + .catch(error => { + this.errorHandler.error(error); + this.loading = false; + }); + } + + openModal(): void { + this.createEditLabel.openModal(); + } + + reload(): void { + this.retrieve(this.scope); + } + + doSearchTargets(targetName: string) { + this.retrieve(this.scope, targetName); + } + + refreshTargets() { + this.retrieve(this.scope); + } + + selectedChange(): void { + // this.forceRefreshView(5000); + } + + editLabel(label: Label[]): void { + this.createEditLabel.editModel(label[0].id, label); + } + + deleteLabels(targets: Label[]): void { + if (targets && targets.length) { + let targetNames: string[] = []; + targets.forEach(target => { + targetNames.push(target.name); + }); + let deletionMessage = new ConfirmationMessage( + 'LABEL.DELETION_TITLE_TARGET', + 'LABEL.DELETION_SUMMARY_TARGET', + targetNames.join(', ') || '', + targets, + ConfirmationTargets.TARGET, + ConfirmationButtons.DELETE_CANCEL); + this.confirmationDialogComponent.open(deletionMessage); + } + } + + confirmDeletion(message: ConfirmationAcknowledgement) { + if (message && + message.source === ConfirmationTargets.TARGET && + message.state === ConfirmationState.CONFIRMED) { + let targetLists: Label[] = message.data; + if (targetLists && targetLists.length) { + let promiseLists: any[] = []; + targetLists.forEach(target => { + promiseLists.push(this.delOperate(target)); + }); + Promise.all(promiseLists).then((item) => { + this.selectedRow = []; + this.retrieve(this.scope); + }); + } + } + } + + delOperate(target: Label) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_LABEL'; + operMessage.data.id = target.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = target.name; + this.operationService.publishInfo(operMessage); + + return toPromise(this.labelService + .deleteLabel(target.id)) + .then( + response => { + this.translateService.get('BATCH.DELETED_SUCCESS') + .subscribe(res => { + operateChanges(operMessage, OperationState.success); + }); + }).catch( + error => { + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + }); + } + + // Forcely refresh the view + forceRefreshView(duration: number): void { + // Reset timer + if (this.timerHandler) { + clearInterval(this.timerHandler); + } + this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => { + if (this.timerHandler) { + clearInterval(this.timerHandler); + this.timerHandler = null; + } + }, duration); } - this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => { - if (this.timerHandler) { - clearInterval(this.timerHandler); - this.timerHandler = null; - } - }, duration); - } } diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html index c3ca177b1..6caead2c8 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html @@ -34,5 +34,5 @@ - - \ No newline at end of file + + diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts index 62069d670..32b49d198 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.spec.ts @@ -13,6 +13,7 @@ import { ReplicationRule } from '../service/interface'; import { ErrorHandler } from '../error-handler/error-handler'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; +import { OperationService } from "../operation/operation.service"; describe('ListReplicationRuleComponent (inline template)', () => { @@ -124,7 +125,8 @@ describe('ListReplicationRuleComponent (inline template)', () => { providers: [ ErrorHandler, { provide: SERVICE_CONFIG, useValue: config }, - { provide: ReplicationService, useClass: ReplicationDefaultService } + { provide: ReplicationService, useClass: ReplicationDefaultService }, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts index b375cdcd4..463d95d93 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -12,283 +12,260 @@ // See the License for the specific language governing permissions and // limitations under the License. import { - Component, - Input, - Output, - OnInit, - EventEmitter, - ViewChild, - ChangeDetectionStrategy, - ChangeDetectorRef, - OnChanges, - SimpleChange, - SimpleChanges + Component, + Input, + Output, + OnInit, + EventEmitter, + ViewChild, + ChangeDetectionStrategy, + ChangeDetectorRef, + OnChanges, + SimpleChange, + SimpleChanges } from "@angular/core"; import { Observable } from "rxjs/Observable"; import "rxjs/add/observable/forkJoin"; import { Comparator } from "clarity-angular"; import { TranslateService } from "@ngx-translate/core"; -import { ReplicationService } from "../service/replication.service"; +import {ReplicationService} from "../service/replication.service"; import { - ReplicationJob, - ReplicationJobItem, - ReplicationRule + ReplicationJob, + ReplicationJobItem, + ReplicationRule } from "../service/interface"; -import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; -import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; -import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; +import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; +import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; +import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; import { - ConfirmationState, - ConfirmationTargets, - ConfirmationButtons + ConfirmationState, + ConfirmationTargets, + ConfirmationButtons } from "../shared/shared.const"; -import { ErrorHandler } from "../error-handler/error-handler"; -import { toPromise, CustomComparator } from "../utils"; -import { - BatchInfo, - BathInfoChanges -} from "../confirmation-dialog/confirmation-batch-message"; +import {ErrorHandler} from "../error-handler/error-handler"; +import {toPromise, CustomComparator} from "../utils"; +import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; +import {OperationService} from "../operation/operation.service"; + @Component({ - selector: "hbr-list-replication-rule", - templateUrl: "./list-replication-rule.component.html", - styleUrls: ["./list-replication-rule.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush + selector: "hbr-list-replication-rule", + templateUrl: "./list-replication-rule.component.html", + styleUrls: ["./list-replication-rule.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class ListReplicationRuleComponent implements OnInit, OnChanges { - nullTime = "0001-01-01T00:00:00Z"; + nullTime = "0001-01-01T00:00:00Z"; - @Input() projectId: number; - @Input() isSystemAdmin: boolean; - @Input() selectedId: number | string; - @Input() withReplicationJob: boolean; + @Input() projectId: number; + @Input() isSystemAdmin: boolean; + @Input() selectedId: number | string; + @Input() withReplicationJob: boolean; - @Input() loading = false; + @Input() loading = false; - @Output() reload = new EventEmitter(); - @Output() selectOne = new EventEmitter(); - @Output() editOne = new EventEmitter(); - @Output() toggleOne = new EventEmitter(); - @Output() hideJobs = new EventEmitter(); - @Output() redirect = new EventEmitter(); - @Output() openNewRule = new EventEmitter(); - @Output() replicateManual = new EventEmitter(); + @Output() reload = new EventEmitter(); + @Output() selectOne = new EventEmitter(); + @Output() editOne = new EventEmitter(); + @Output() toggleOne = new EventEmitter(); + @Output() hideJobs = new EventEmitter(); + @Output() redirect = new EventEmitter(); + @Output() openNewRule = new EventEmitter(); + @Output() replicateManual = new EventEmitter(); - projectScope = false; + projectScope = false; - rules: ReplicationRule[]; - changedRules: ReplicationRule[]; - ruleName: string; - canDeleteRule: boolean; + rules: ReplicationRule[]; + changedRules: ReplicationRule[]; + ruleName: string; + canDeleteRule: boolean; - selectedRow: ReplicationRule; - batchDelectionInfos: BatchInfo[] = []; + selectedRow: ReplicationRule; - @ViewChild("toggleConfirmDialog") - toggleConfirmDialog: ConfirmationDialogComponent; + @ViewChild("toggleConfirmDialog") + toggleConfirmDialog: ConfirmationDialogComponent; - @ViewChild("deletionConfirmDialog") - deletionConfirmDialog: ConfirmationDialogComponent; + @ViewChild("deletionConfirmDialog") + deletionConfirmDialog: ConfirmationDialogComponent; - startTimeComparator: Comparator = new CustomComparator< - ReplicationRule - >("start_time", "date"); - enabledComparator: Comparator = new CustomComparator< - ReplicationRule - >("enabled", "number"); + startTimeComparator: Comparator = new CustomComparator("start_time", "date"); + enabledComparator: Comparator = new CustomComparator("enabled", "number"); - constructor( - private replicationService: ReplicationService, - private translateService: TranslateService, - private errorHandler: ErrorHandler, - private ref: ChangeDetectorRef - ) { - setInterval(() => ref.markForCheck(), 500); - } - - trancatedDescription(desc: string): string { - if (desc.length > 35) { - return desc.substr(0, 35); - } else { - return desc; + constructor(private replicationService: ReplicationService, + private translateService: TranslateService, + private errorHandler: ErrorHandler, + private operationService: OperationService, + private ref: ChangeDetectorRef) { + setInterval(() => ref.markForCheck(), 500); } - } - ngOnInit(): void { - // Global scope - if (!this.projectScope) { - this.retrieveRules(); - } - } - - ngOnChanges(changes: SimpleChanges): void { - let proIdChange: SimpleChange = changes["projectId"]; - if (proIdChange) { - if (proIdChange.currentValue !== proIdChange.previousValue) { - if (proIdChange.currentValue) { - this.projectId = proIdChange.currentValue; - this.projectScope = true; // Scope is project, not global list - // Initially load the replication rule data - this.retrieveRules(); + trancatedDescription(desc: string): string { + if (desc.length > 35) { + return desc.substr(0, 35); + } else { + return desc; } - } } - } - retrieveRules(ruleName = ""): void { - this.loading = true; - /*this.selectedRow = null;*/ - toPromise( - this.replicationService.getReplicationRules(this.projectId, ruleName) - ) - .then(rules => { - this.rules = rules || []; - // job list hidden - this.hideJobs.emit(); - this.changedRules = this.rules; - this.loading = false; - }) - .catch(error => { - this.errorHandler.error(error); - this.loading = false; - }); - } - - replicateRule(rules: ReplicationRule[]): void { - this.replicateManual.emit(rules); - } - - deletionConfirm(message: ConfirmationAcknowledgement) { - if ( - message && - message.source === ConfirmationTargets.POLICY && - message.state === ConfirmationState.CONFIRMED - ) { - this.deleteOpe(message.data); + ngOnInit(): void { + // Global scope + if (!this.projectScope) { + this.retrieveRules(); + } } - } - selectRule(rule: ReplicationRule): void { - this.selectedId = rule.id || ""; - this.selectOne.emit(rule); - } - - redirectTo(rule: ReplicationRule): void { - this.redirect.emit(rule); - } - - openModal(): void { - this.openNewRule.emit(); - } - - editRule(rule: ReplicationRule) { - this.editOne.emit(rule); - } - - jobList(id: string | number): Promise { - let ruleData: ReplicationJobItem[]; - this.canDeleteRule = true; - let count = 0; - return toPromise(this.replicationService.getJobs(id)) - .then(response => { - ruleData = response.data; - if (ruleData.length) { - ruleData.forEach(job => { - if ( - job.status === "pending" || - job.status === "running" || - job.status === "retrying" - ) { - count++; + ngOnChanges(changes: SimpleChanges): void { + let proIdChange: SimpleChange = changes["projectId"]; + if (proIdChange) { + if (proIdChange.currentValue !== proIdChange.previousValue) { + if (proIdChange.currentValue) { + this.projectId = proIdChange.currentValue; + this.projectScope = true; // Scope is project, not global list + // Initially load the replication rule data + this.retrieveRules(); + } } - }); } - this.canDeleteRule = count > 0 ? false : true; - }) - .catch(error => this.errorHandler.error(error)); - } - - deleteRule(rule: ReplicationRule) { - if (rule) { - this.batchDelectionInfos = []; - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = rule.name; - this.batchDelectionInfos.push(initBatchMessage); - let deletionMessage = new ConfirmationMessage( - "REPLICATION.DELETION_TITLE", - "REPLICATION.DELETION_SUMMARY", - rule.name, - rule, - ConfirmationTargets.POLICY, - ConfirmationButtons.DELETE_CANCEL - ); - this.deletionConfirmDialog.open(deletionMessage); } - } - deleteOpe(rule: ReplicationRule) { - if (rule) { - let promiseLists: any[] = []; - Promise.all([this.jobList(rule.id)]).then(items => { + + retrieveRules(ruleName = ""): void { + this.loading = true; + /*this.selectedRow = null;*/ + toPromise( + this.replicationService.getReplicationRules(this.projectId, ruleName) + ) + .then(rules => { + this.rules = rules || []; + // job list hidden + this.hideJobs.emit(); + this.changedRules = this.rules; + this.loading = false; + }) + .catch(error => { + this.errorHandler.error(error); + this.loading = false; + }); + } + + replicateRule(rules: ReplicationRule[]): void { + this.replicateManual.emit(rules); + } + + deletionConfirm(message: ConfirmationAcknowledgement) { + if ( + message && + message.source === ConfirmationTargets.POLICY && + message.state === ConfirmationState.CONFIRMED + ) { + this.deleteOpe(message.data); + } + } + + selectRule(rule: ReplicationRule): void { + this.selectedId = rule.id || ""; + this.selectOne.emit(rule); + } + + redirectTo(rule: ReplicationRule): void { + this.redirect.emit(rule); + } + + openModal(): void { + this.openNewRule.emit(); + } + + editRule(rule: ReplicationRule) { + this.editOne.emit(rule); + } + + jobList(id: string | number): Promise { + let ruleData: ReplicationJobItem[]; + this.canDeleteRule = true; + let count = 0; + return toPromise(this.replicationService.getJobs(id)) + .then(response => { + ruleData = response.data; + if (ruleData.length) { + ruleData.forEach(job => { + if ( + job.status === "pending" || + job.status === "running" || + job.status === "retrying" + ) { + count++; + } + }); + } + this.canDeleteRule = count > 0 ? false : true; + }) + .catch(error => this.errorHandler.error(error)); + } + + deleteRule(rule: ReplicationRule) { + if (rule) { + let deletionMessage = new ConfirmationMessage( + "REPLICATION.DELETION_TITLE", + "REPLICATION.DELETION_SUMMARY", + rule.name, + rule, + ConfirmationTargets.POLICY, + ConfirmationButtons.DELETE_CANCEL + ); + this.deletionConfirmDialog.open(deletionMessage); + } + } + + deleteOpe(rule: ReplicationRule) { + if (rule) { + let promiseLists: any[] = []; + Promise.all([this.jobList(rule.id)]).then(items => { + promiseLists.push(this.delOperate(rule)); + + Promise.all(promiseLists).then(item => { + this.selectedRow = null; + this.reload.emit(true); + let hnd = setInterval(() => this.ref.markForCheck(), 200); + setTimeout(() => clearInterval(hnd), 2000); + }); + }); + } + } + + delOperate(rule: ReplicationRule) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_REPLICATION'; + operMessage.data.id = +rule.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = rule.name; + this.operationService.publishInfo(operMessage); + if (!this.canDeleteRule) { - let findedList = this.batchDelectionInfos.find( - data => data.name === rule.name - ); - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get("REPLICATION.DELETION_SUMMARY_FAILURE") - ).subscribe(res => { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - } else { - promiseLists.push(this.delOperate(+rule.id, rule.name)); + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPLICATION.DELETION_SUMMARY_FAILURE')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + return null; } - Promise.all(promiseLists).then(item => { - this.selectedRow = null; - this.reload.emit(true); - let hnd = setInterval(() => this.ref.markForCheck(), 200); - setTimeout(() => clearInterval(hnd), 2000); - }); - }); + return toPromise(this.replicationService + .deleteReplicationRule(+rule.id)) + .then(() => { + this.translateService.get('BATCH.DELETED_SUCCESS') + .subscribe(res => operateChanges(operMessage, OperationState.success)); + }) + .catch(error => { + if (error && error.status === 412) { + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + } else { + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + } + }); } - } - - delOperate(ruleId: number, name: string) { - let findedList = this.batchDelectionInfos.find(data => data.name === name); - return toPromise(this.replicationService.deleteReplicationRule(ruleId)) - .then(() => { - this.translateService - .get("BATCH.DELETED_SUCCESS") - .subscribe(res => (findedList = BathInfoChanges(findedList, res))); - }) - .catch(error => { - if (error && error.status === 412) { - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get( - "REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED" - ) - ).subscribe(res => { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - } else { - this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); - }); - } - }); - } } diff --git a/src/ui_ng/lib/src/operation/index.ts b/src/ui_ng/lib/src/operation/index.ts new file mode 100644 index 000000000..db80be253 --- /dev/null +++ b/src/ui_ng/lib/src/operation/index.ts @@ -0,0 +1,13 @@ +/** + * Created by pengf on 5/11/2018. + */ + +import {Type} from "@angular/core"; +import {OperationComponent} from "./operation.component"; + +export * from "./operation.component"; +export * from './operate'; +export * from './operation.service'; +export const OPERATION_DIRECTIVES: Type[] = [ + OperationComponent +]; diff --git a/src/ui_ng/lib/src/operation/operate.ts b/src/ui_ng/lib/src/operation/operate.ts new file mode 100644 index 000000000..8bb17e01a --- /dev/null +++ b/src/ui_ng/lib/src/operation/operate.ts @@ -0,0 +1,30 @@ +export class OperateInfo { + name: string; + state: string; + data: {[key: string]: string| number}; + timeStamp: number; + timeDiff: string; + constructor() { + this.name = ''; + this.state = ''; + this.data = {id: -1, name: '', errorInf: ''}; + this.timeStamp = 0; + this.timeDiff = 'less 1 minute'; + } +} + +export function operateChanges(list: OperateInfo, state?: string, errorInfo?: string, timeStamp?: 0) { + list.state = state; + list.data.errorInf = errorInfo; + list.timeStamp = new Date().getTime(); + return list; +} + + +export const OperationState = { + progressing: 'progressing', + success : 'success', + failure : 'failure', + interrupt: 'interrupt' +}; + diff --git a/src/ui_ng/lib/src/operation/operation.component.css b/src/ui_ng/lib/src/operation/operation.component.css new file mode 100644 index 000000000..7a53fdab0 --- /dev/null +++ b/src/ui_ng/lib/src/operation/operation.component.css @@ -0,0 +1,43 @@ +/* side form */ +.side-form { + position: absolute; + width: 325px; + height: 100%; + left:28px; + padding-top: 20px; + background: #fff; + border-left: 1px solid #e0e0e0; +} +.eventInfo {display: flex; justify-content: flex-start; align-content: flex-start; + padding: 8px 5px 8px 10px; border-bottom: 1px solid #ccc;} +.iconsArea{ flex-shrink: 1;} +.infoArea{ margin-left: 10px; width: 270px;} +.eventName{display: block; margin-bottom: -5px;font-size: 16px; color: rgb(11, 127, 189); } +.eventErrorInf {display:block; font-size: 12px;color:red;line-height: .6rem;} +.eventTarget{display: inline-flex; width: 172px; font-size: 12px; flex-shrink:1; overflow: hidden; text-overflow: ellipsis;white-space: nowrap;} +.eventTime{ float: right; font-size: 12px;} +:host >>> .nav{padding-left: 38px;} +.operDiv{position: fixed; top: 60px; right: 0; z-index:100} +.toolBar{ + float: left;border-top: 1px solid #ccc; + transform: rotate(-90deg); + margin-top: 10px; + padding: 2px 4px; + width: 86px; + height: 84px; + background-color: white; + letter-spacing: 1.2px; + font-weight: 500; + box-shadow: -2px -1px 3px #bebbbb; + cursor: pointer; + text-align: center; + text-decoration: none; + } +.freshIcon{float: right; margin-right: 20px; margin-top: -10px;cursor: pointer;} +#contentFailed, #contentAll, #contentRun{ + position: absolute; + top: 95px; + bottom: 0; + width: 100%; + overflow-y: auto; +} diff --git a/src/ui_ng/lib/src/operation/operation.component.html b/src/ui_ng/lib/src/operation/operation.component.html new file mode 100644 index 000000000..1f3809cdd --- /dev/null +++ b/src/ui_ng/lib/src/operation/operation.component.html @@ -0,0 +1,64 @@ +
+ {{'OPERATION.EVENT_LOG' | translate}} +
+ +

{{'OPERATION.LOCAL_EVENT' | translate}}

+
+ + + + +
+
+ + + + +
+
+ + {{list.data.name}}{{list.timeDiff | translate}} + +
+
+
+
+ + + +
+
+ + + +
+
+ + {{list.data.name}}{{list.timeDiff | translate}} + +
+
+
+
+ + + +
+
+ + + +
+
+ + {{list.data.name}}{{list.timeDiff | translate}} + +
+
+
+
+
+
+
+
+ diff --git a/src/ui_ng/lib/src/operation/operation.component.ts b/src/ui_ng/lib/src/operation/operation.component.ts new file mode 100644 index 000000000..c14c7070a --- /dev/null +++ b/src/ui_ng/lib/src/operation/operation.component.ts @@ -0,0 +1,144 @@ +import {Component, OnInit, OnDestroy, HostListener} from '@angular/core'; +import {OperationService} from "./operation.service"; +import {Subscription} from "rxjs/Subscription"; +import {OperateInfo, OperationState} from "./operate"; +import {SlideInOutAnimation} from "../_animations/slide-in-out.animation"; +import {TranslateService} from "@ngx-translate/core"; + + +@Component({ + selector: 'hbr-operation-model', + templateUrl: './operation.component.html', + styleUrls: ['./operation.component.css'], + animations: [SlideInOutAnimation], +}) +export class OperationComponent implements OnInit, OnDestroy { + batchInfoSubscription: Subscription; + resultLists: OperateInfo[] = []; + animationState = "out"; + + @HostListener('window:beforeunload', ['$event']) + beforeUnloadHander(event) { + // storage to localStorage + let timp = new Date().getTime(); + localStorage.setItem('operaion', JSON.stringify({timp: timp, data: this.resultLists})); + } + + constructor( + private operationService: OperationService, + private translate: TranslateService) { + + this.batchInfoSubscription = operationService.operationInfo$.subscribe(data => { + // this.resultLists = data; + this.openSlide(); + if (data) { + if (this.resultLists.length >= 50) { + this.resultLists.splice(49, this.resultLists.length - 49); + } + this.resultLists.unshift(data); + } + }); + } + + public get runningLists(): OperateInfo[] { + let runningList: OperateInfo[] = []; + this.resultLists.forEach(data => { + if (data.state === 'progressing') { + runningList.push(data); + } + }); + return runningList; + } + + public get failLists(): OperateInfo[] { + let failedList: OperateInfo[] = []; + this.resultLists.forEach(data => { + if (data.state === 'failure') { + failedList.push(data); + } + }); + return failedList; + } + + ngOnInit() { + let requestCookie = localStorage.getItem('operaion'); + if (requestCookie) { + let operInfors: any = JSON.parse(requestCookie); + if (operInfors) { + if ((new Date().getTime() - operInfors.timp) > 1000 * 60 * 60 * 24) { + localStorage.removeItem('operaion'); + }else { + if (operInfors.data) { + operInfors.data.forEach(operInfo => { + if (operInfo.state === OperationState.progressing) { + operInfo.state = OperationState.interrupt; + operInfo.data.errorInf = 'operation been interrupted'; + } + }); + this.resultLists = operInfors.data; + } + } + } + + } + } + ngOnDestroy(): void { + if (this.batchInfoSubscription) { + this.batchInfoSubscription.unsubscribe(); + } + } + + toggleTitle(errorSpan: any) { + errorSpan.style.display = (errorSpan.style.display === 'none') ? 'block' : 'none'; + } + + slideOut(): void { + this.animationState = this.animationState === 'out' ? 'in' : 'out'; + } + + openSlide(): void { + this.animationState = 'in'; + } + + + TabEvent(): void { + let timp: any; + this.resultLists.forEach(data => { + timp = new Date().getTime() - +data.timeStamp; + data.timeDiff = this.calculateTime(timp); + }); + } + + calculateTime(timp: number) { + let dist = Math.floor(timp / 1000 / 60); // change to minute; + if (dist > 0 && dist < 60) { + return Math.floor(dist) + ' minute(s) ago'; + }else if (dist >= 60 && Math.floor(dist / 60) < 24) { + return Math.floor(dist / 60) + ' hour(s) ago'; + } else if (Math.floor(dist / 60) >= 24) { + return Math.floor(dist / 60 / 24) + ' day ago'; + } else { + return 'less 1 minute'; + } + + } + + /*calculateTime(timp: number) { + let dist = Math.floor(timp / 1000 / 60); // change to minute; + if (dist > 0 && dist < 60) { + return this.translateTime('OPERATION.MINUTE_AGO', Math.floor(dist)); + }else if (dist > 60 && Math.floor(dist / 60) < 24) { + return this.translateTime('OPERATION.HOUR_AGO', Math.floor(dist / 60)); + } else if (Math.floor(dist / 60) >= 24 && Math.floor(dist / 60) <= 48) { + return this.translateTime('OPERATION.DAY_AGO', Math.floor(dist / 60 / 24)); + } else { + return this.translateTime('OPERATION.SECOND_AGO'); + } + }*/ + + translateTime(tim: string, param?: number) { + this.translate.get(tim, { 'param': param }).subscribe((res: string) => { + return res; + }); + } +} diff --git a/src/ui_ng/lib/src/operation/operation.service.ts b/src/ui_ng/lib/src/operation/operation.service.ts new file mode 100644 index 000000000..3e81fa0e9 --- /dev/null +++ b/src/ui_ng/lib/src/operation/operation.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import {OperateInfo} from "./operate"; + +@Injectable() +export class OperationService { + subjects: Subject = null; + + operationInfoSource = new Subject(); + operationInfo$ = this.operationInfoSource.asObservable(); + + publishInfo(data: OperateInfo): void { + this.operationInfoSource.next(data); + } +} diff --git a/src/ui_ng/lib/src/replication/replication.component.html b/src/ui_ng/lib/src/replication/replication.component.html index 689400ab2..a81de3b09 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html +++ b/src/ui_ng/lib/src/replication/replication.component.html @@ -73,5 +73,6 @@ - - \ No newline at end of file + + + diff --git a/src/ui_ng/lib/src/replication/replication.component.spec.ts b/src/ui_ng/lib/src/replication/replication.component.spec.ts index 011941cce..1f0f1b019 100644 --- a/src/ui_ng/lib/src/replication/replication.component.spec.ts +++ b/src/ui_ng/lib/src/replication/replication.component.spec.ts @@ -20,6 +20,7 @@ import { EndpointService, EndpointDefaultService } from '../service/endpoint.ser import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index'; import {ProjectDefaultService, ProjectService} from "../service/project.service"; +import {OperationService} from "../operation/operation.service"; describe('Replication Component (inline template)', () => { @@ -231,7 +232,8 @@ describe('Replication Component (inline template)', () => { { provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService }, { provide: ProjectService, useClass: ProjectDefaultService }, - { provide: JobLogService, useClass: JobLogDefaultService } + { provide: JobLogService, useClass: JobLogDefaultService }, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index 492cb7ef7..b8fa3f73f 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -56,12 +56,10 @@ import { ConfirmationState } from "../shared/shared.const"; import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; -import { - BatchInfo, - BathInfoChanges -} from "../confirmation-dialog/confirmation-batch-message"; import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; +import {operateChanges, OperationState, OperateInfo} from "../operation/operate"; +import {OperationService} from "../operation/operation.service"; const ruleStatus: { [key: string]: any } = [ { key: "all", description: "REPLICATION.ALL_STATUS" }, @@ -130,7 +128,6 @@ export class ReplicationComponent implements OnInit, OnDestroy { hiddenJobList = true; jobs: ReplicationJobItem[]; - batchDelectionInfos: BatchInfo[] = []; toggleJobSearchOption = optionalSearch; currentJobSearchOption: number; @@ -165,8 +162,8 @@ export class ReplicationComponent implements OnInit, OnDestroy { constructor( private errorHandler: ErrorHandler, private replicationService: ReplicationService, - private translateService: TranslateService - ) {} + private operationService: OperationService, + private translateService: TranslateService) {} public get showPaginationIndex(): boolean { return this.totalCount > 0; @@ -307,10 +304,6 @@ export class ReplicationComponent implements OnInit, OnDestroy { replicateManualRule(rule: ReplicationRule) { if (rule) { - this.batchDelectionInfos = []; - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = rule.name; - this.batchDelectionInfos.push(initBatchMessage); let replicationMessage = new ConfirmationMessage( "REPLICATION.REPLICATION_TITLE", "REPLICATION.REPLICATION_SUMMARY", @@ -332,43 +325,37 @@ export class ReplicationComponent implements OnInit, OnDestroy { let rule: ReplicationRule = message.data; if (rule) { - Promise.all([this.replicationOperate(+rule.id, rule.name)]).then( - item => { - this.selectOneRule(rule); - } - ); + Promise.all([this.replicationOperate(rule)]).then((item) => { + this.selectOneRule(rule); + }); } } } - replicationOperate(ruleId: number, name: string) { - let findedList = this.batchDelectionInfos.find(data => data.name === name); + replicationOperate(rule: ReplicationRule) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.REPLICATION'; + operMessage.data.id = rule.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = rule.name; + this.operationService.publishInfo(operMessage); - return toPromise(this.replicationService.replicateRule(ruleId)) - .then(response => { - this.translateService - .get("BATCH.REPLICATE_SUCCESS") - .subscribe(res => (findedList = BathInfoChanges(findedList, res))); - }) - .catch(error => { - if (error && error.status === 412) { - Observable.forkJoin( - this.translateService.get("BATCH.REPLICATE_FAILURE"), - this.translateService.get("REPLICATION.REPLICATE_SUMMARY_FAILURE") - ).subscribe(function(res) { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - } else { - this.translateService - .get("BATCH.REPLICATE_FAILURE") - .subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); + return toPromise(this.replicationService.replicateRule(+rule.id)) + .then(response => { + this.translateService.get('BATCH.REPLICATE_SUCCESS') + .subscribe(res => operateChanges(operMessage, OperationState.success)); + }) + .catch(error => { + if (error && error.status === 412) { + Observable.forkJoin(this.translateService.get('BATCH.REPLICATE_FAILURE'), + this.translateService.get('REPLICATION.REPLICATE_SUMMARY_FAILURE')) + .subscribe(function (res) { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + } else { + this.translateService.get('BATCH.REPLICATE_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); }); } }); diff --git a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.html b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.html index 18888af37..7f9c330dd 100644 --- a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.html +++ b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.html @@ -17,33 +17,36 @@ -
- - - - - - - {{'REPOSITORY.NAME' | translate}} - {{'REPOSITORY.TAGS_COUNT' | translate}} - {{'REPOSITORY.PULL_COUNT' | translate}} - {{'REPOSITORY.PLACEHOLDER' | translate }} - - {{r.name}} - {{r.tags_count}} - {{r.pull_count}} - - +
+
+ + + + + + + {{'REPOSITORY.NAME' | translate}} + {{'REPOSITORY.TAGS_COUNT' | translate}} + {{'REPOSITORY.PULL_COUNT' | translate}} + {{'REPOSITORY.PLACEHOLDER' | translate }} + + {{r.name}} + {{r.tags_count}} + {{r.pull_count}} + + {{'CONFIG.SCANNING.DB_NOT_READY' | translate }} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} - {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}} - - - + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}} + + + +
+ @@ -90,5 +93,5 @@ - +
\ No newline at end of file diff --git a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.spec.ts b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.spec.ts index 536447aa7..c73a77582 100644 --- a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.spec.ts +++ b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.spec.ts @@ -22,6 +22,7 @@ import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index'; import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index'; import { JobLogViewerComponent } from '../job-log-viewer/index'; import {LabelPieceComponent} from "../label-piece/label-piece.component"; +import {OperationService} from "../operation/operation.service"; describe('RepositoryComponentGridview (inline template)', () => { @@ -115,7 +116,8 @@ describe('RepositoryComponentGridview (inline template)', () => { { provide: SERVICE_CONFIG, useValue: config }, { provide: RepositoryService, useClass: RepositoryDefaultService }, { provide: TagService, useClass: TagDefaultService }, - { provide: SystemInfoService, useClass: SystemInfoDefaultService } + { provide: SystemInfoService, useClass: SystemInfoDefaultService }, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.ts b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.ts index eebaab31c..9b72b4d30 100644 --- a/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.ts +++ b/src/ui_ng/lib/src/repository-gridview/repository-gridview.component.ts @@ -1,559 +1,496 @@ import { - Component, - Input, - Output, - OnInit, - ViewChild, - ChangeDetectionStrategy, - ChangeDetectorRef, - EventEmitter, - OnChanges, - SimpleChanges + Component, + Input, + Output, + OnInit, + ViewChild, + ChangeDetectionStrategy, + ChangeDetectorRef, + EventEmitter, + OnChanges, + SimpleChanges } from "@angular/core"; -import { Router } from "@angular/router"; -import { Observable } from "rxjs/Observable"; +import {Router} from "@angular/router"; +import {Observable} from "rxjs/Observable"; import "rxjs/add/observable/forkJoin"; -import { TranslateService } from "@ngx-translate/core"; -import { Comparator, State } from "clarity-angular"; +import {TranslateService} from "@ngx-translate/core"; +import {Comparator, State} from "clarity-angular"; import { - Repository, - SystemInfo, - SystemInfoService, - RepositoryService, - RequestQueryParams, - RepositoryItem, - TagService -} from "../service/index"; -import { ErrorHandler } from "../error-handler/error-handler"; -import { - toPromise, - CustomComparator, - DEFAULT_PAGE_SIZE, - calculatePage, - doFiltering, - doSorting, - clone -} from "../utils"; -import { - ConfirmationState, - ConfirmationTargets, - ConfirmationButtons -} from "../shared/shared.const"; -import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; -import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; -import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; -import { Tag } from "../service/interface"; -import { - BatchInfo, - BathInfoChanges -} from "../confirmation-dialog/confirmation-batch-message"; -import { GridViewComponent } from "../gridview/grid-view.component"; + Repository, + SystemInfo, + SystemInfoService, + RepositoryService, + RequestQueryParams, + RepositoryItem, + TagService +} from '../service/index'; +import {ErrorHandler} from '../error-handler/error-handler'; +import {toPromise, CustomComparator, DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting, clone} from '../utils'; +import {ConfirmationState, ConfirmationTargets, ConfirmationButtons} from '../shared/shared.const'; +import {ConfirmationDialogComponent} from '../confirmation-dialog/confirmation-dialog.component'; +import {ConfirmationMessage} from '../confirmation-dialog/confirmation-message'; +import {ConfirmationAcknowledgement} from '../confirmation-dialog/confirmation-state-message'; +import {Tag} from '../service/interface'; +import {GridViewComponent} from '../gridview/grid-view.component'; +import {OperationService} from "../operation/operation.service"; +import {OperateInfo, OperationState, operateChanges} from "../operation/operate"; @Component({ - selector: "hbr-repository-gridview", - templateUrl: "./repository-gridview.component.html", - styleUrls: ["./repository-gridview.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush + selector: "hbr-repository-gridview", + templateUrl: "./repository-gridview.component.html", + styleUrls: ["./repository-gridview.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class RepositoryGridviewComponent implements OnChanges, OnInit { - signedCon: { [key: string]: any | string[] } = {}; - @Input() projectId: number; - @Input() projectName = "unknown"; - @Input() urlPrefix: string; - @Input() hasSignedIn: boolean; - @Input() hasProjectAdminRole: boolean; - @Input() mode = "admiral"; - @Output() repoClickEvent = new EventEmitter(); - @Output() repoProvisionEvent = new EventEmitter(); - @Output() addInfoEvent = new EventEmitter(); + signedCon: { [key: string]: any | string[] } = {}; + @Input() projectId: number; + @Input() projectName = "unknown"; + @Input() urlPrefix: string; + @Input() hasSignedIn: boolean; + @Input() hasProjectAdminRole: boolean; + @Input() mode = "admiral"; + @Output() repoClickEvent = new EventEmitter(); + @Output() repoProvisionEvent = new EventEmitter(); + @Output() addInfoEvent = new EventEmitter(); - lastFilteredRepoName: string; - repositories: RepositoryItem[] = []; - repositoriesCopy: RepositoryItem[] = []; - systemInfo: SystemInfo; - selectedRow: RepositoryItem[] = []; - loading = true; + lastFilteredRepoName: string; + repositories: RepositoryItem[] = []; + repositoriesCopy: RepositoryItem[] = []; + systemInfo: SystemInfo; + selectedRow: RepositoryItem[] = []; + loading = true; - isCardView: boolean; - cardHover = false; - listHover = false; + isCardView: boolean; + cardHover = false; + listHover = false; - batchDelectionInfos: BatchInfo[] = []; - pullCountComparator: Comparator = new CustomComparator< - RepositoryItem - >("pull_count", "number"); - tagsCountComparator: Comparator = new CustomComparator< - RepositoryItem - >("tags_count", "number"); + pullCountComparator: Comparator = new CustomComparator('pull_count', 'number'); + tagsCountComparator: Comparator = new CustomComparator('tags_count', 'number'); - pageSize: number = DEFAULT_PAGE_SIZE; - currentPage = 1; - totalCount = 0; - currentState: State; + pageSize: number = DEFAULT_PAGE_SIZE; + currentPage = 1; + totalCount = 0; + currentState: State; - @ViewChild("confirmationDialog") - confirmationDialog: ConfirmationDialogComponent; + @ViewChild("confirmationDialog") + confirmationDialog: ConfirmationDialogComponent; - @ViewChild("gridView") gridView: GridViewComponent; + @ViewChild("gridView") gridView: GridViewComponent; - constructor( - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private repositoryService: RepositoryService, - private systemInfoService: SystemInfoService, - private tagService: TagService, - private ref: ChangeDetectorRef, - private router: Router - ) {} - - public get registryUrl(): string { - return this.systemInfo ? this.systemInfo.registry_url : ""; - } - - public get withClair(): boolean { - return this.systemInfo ? this.systemInfo.with_clair : false; - } - - public get isClairDBReady(): boolean { - return ( - this.systemInfo && - this.systemInfo.clair_vulnerability_status && - this.systemInfo.clair_vulnerability_status.overall_last_update > 0 - ); - } - - public get withAdmiral(): boolean { - return this.mode === "admiral"; - } - - public get showDBStatusWarning(): boolean { - return this.withClair && !this.isClairDBReady; - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes["projectId"] && changes["projectId"].currentValue) { - this.refresh(); - } - } - - ngOnInit(): void { - // Get system info for tag views - toPromise(this.systemInfoService.getSystemInfo()) - .then(systemInfo => (this.systemInfo = systemInfo)) - .catch(error => this.errorHandler.error(error)); - - if (this.mode === "admiral") { - this.isCardView = true; - } else { - this.isCardView = false; + constructor(private errorHandler: ErrorHandler, + private translateService: TranslateService, + private repositoryService: RepositoryService, + private systemInfoService: SystemInfoService, + private tagService: TagService, + private operationService: OperationService, + private ref: ChangeDetectorRef, + private router: Router) { } - this.lastFilteredRepoName = ""; - } + public get registryUrl(): string { + return this.systemInfo ? this.systemInfo.registry_url : ""; + } - confirmDeletion(message: ConfirmationAcknowledgement) { - if ( - message && - message.source === ConfirmationTargets.REPOSITORY && - message.state === ConfirmationState.CONFIRMED - ) { - let promiseLists: any[] = []; - let repoNames: string[] = message.data.split(","); + public get withClair(): boolean { + return this.systemInfo ? this.systemInfo.with_clair : false; + } - repoNames.forEach(repoName => { - promiseLists.push(this.delOperate(repoName)); - }); + public get isClairDBReady(): boolean { + return ( + this.systemInfo && + this.systemInfo.clair_vulnerability_status && + this.systemInfo.clair_vulnerability_status.overall_last_update > 0 + ); + } - Promise.all(promiseLists).then(item => { - this.selectedRow = []; - this.refresh(); - let st: State = this.getStateAfterDeletion(); - if (!st) { - this.refresh(); + public get withAdmiral(): boolean { + return this.mode === "admiral"; + } + + public get showDBStatusWarning(): boolean { + return this.withClair && !this.isClairDBReady; + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes["projectId"] && changes["projectId"].currentValue) { + this.refresh(); + } + } + + ngOnInit(): void { + // Get system info for tag views + toPromise(this.systemInfoService.getSystemInfo()) + .then(systemInfo => (this.systemInfo = systemInfo)) + .catch(error => this.errorHandler.error(error)); + + if (this.mode === "admiral") { + this.isCardView = true; } else { - this.clrLoad(st); + this.isCardView = false; } - }); + + this.lastFilteredRepoName = ""; } - } - delOperate(repoName: string) { - let findedList = this.batchDelectionInfos.find( - data => data.name === repoName - ); - if (this.signedCon[repoName].length !== 0) { - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get("REPOSITORY.DELETION_TITLE_REPO_SIGNED") - ).subscribe(res => { - findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); - }); - } else { - return toPromise( - this.repositoryService.deleteRepository(repoName) - ) - .then(response => { - this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { - findedList = BathInfoChanges(findedList, res); - }); - }) - .catch(error => { - if (error.status === "412") { - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get("REPOSITORY.TAGS_SIGNED") - ).subscribe(res => { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - return; - } - if (error.status === 503) { - Observable.forkJoin( - this.translateService.get("BATCH.DELETED_FAILURE"), - this.translateService.get("REPOSITORY.TAGS_NO_DELETE") - ).subscribe(res => { - findedList = BathInfoChanges( - findedList, - res[0], - false, - true, - res[1] - ); - }); - return; - } - this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); - }); - }); - } - } + confirmDeletion(message: ConfirmationAcknowledgement) { + if (message && + message.source === ConfirmationTargets.REPOSITORY && + message.state === ConfirmationState.CONFIRMED) { - doSearchRepoNames(repoName: string) { - this.lastFilteredRepoName = repoName; - this.currentPage = 1; - let st: State = this.currentState; - if (!st) { - st = { page: {} }; - } - st.page.size = this.pageSize; - st.page.from = 0; - st.page.to = this.pageSize - 1; - this.clrLoad(st); - } + let repoLists = message.data; + if (repoLists && repoLists.length) { + let promiseLists: any[] = []; + repoLists.forEach(repo => { + promiseLists.push(this.delOperate(repo)); + }); - saveSignatures(event: { [key: string]: string[] }): void { - Object.assign(this.signedCon, event); - } - - deleteRepos(repoLists: RepositoryItem[]) { - if (repoLists && repoLists.length) { - let repoNames: string[] = []; - this.batchDelectionInfos = []; - let repArr: any[] = []; - - repoLists.forEach(repo => { - repoNames.push(repo.name); - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = repo.name; - this.batchDelectionInfos.push(initBatchMessage); - - if (!this.signedCon[repo.name]) { - repArr.push(this.getTagInfo(repo.name)); + Promise.all(promiseLists).then((item) => { + this.selectedRow = []; + this.refresh(); + let st: State = this.getStateAfterDeletion(); + if (!st) { + this.refresh(); + } else { + this.clrLoad(st); + } + }); + } } - }); - - Promise.all(repArr).then(() => { - this.confirmationDialogSet( - "REPOSITORY.DELETION_TITLE_REPO", - "", - repoNames.join(","), - "REPOSITORY.DELETION_SUMMARY_REPO", - ConfirmationButtons.DELETE_CANCEL - ); - }); } - } - getTagInfo(repoName: string): Promise { - this.signedCon[repoName] = []; - return toPromise(this.tagService.getTags(repoName)) - .then(items => { - items.forEach((t: Tag) => { - if (t.signature !== null) { - this.signedCon[repoName].push(t.name); - } - }); - }) - .catch(error => this.errorHandler.error(error)); - } + delOperate(repo: RepositoryItem) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_REPO'; + operMessage.data.id = repo.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = repo.name; + this.operationService.publishInfo(operMessage); - signedDataSet(repoName: string): void { - let signature = ""; - if (this.signedCon[repoName].length === 0) { - this.confirmationDialogSet( - "REPOSITORY.DELETION_TITLE_REPO", - signature, - repoName, - "REPOSITORY.DELETION_SUMMARY_REPO", - ConfirmationButtons.DELETE_CANCEL - ); - return; + if (this.signedCon[repo.name].length !== 0) { + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + } else { + return toPromise(this.repositoryService + .deleteRepository(repo.name)) + .then( + response => { + this.translateService.get('BATCH.DELETED_SUCCESS').subscribe(res => { + operateChanges(operMessage, OperationState.success); + }); + }).catch(error => { + if (error.status === "412") { + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + return; + } + if (error.status === 503) { + Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + return; + } + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + }); + } } - signature = this.signedCon[repoName].join(","); - this.confirmationDialogSet( - "REPOSITORY.DELETION_TITLE_REPO_SIGNED", - signature, - repoName, - "REPOSITORY.DELETION_SUMMARY_REPO_SIGNED", - ConfirmationButtons.CLOSE - ); - } - confirmationDialogSet( - summaryTitle: string, - signature: string, - repoName: string, - summaryKey: string, - button: ConfirmationButtons - ): void { - this.translateService - .get(summaryKey, { - repoName: repoName, - signedImages: signature - }) - .subscribe((res: string) => { - summaryKey = res; - let message = new ConfirmationMessage( - summaryTitle, - summaryKey, - repoName, - repoName, - ConfirmationTargets.REPOSITORY, - button - ); - this.confirmationDialog.open(message); + doSearchRepoNames(repoName: string) { + this.lastFilteredRepoName = repoName; + this.currentPage = 1; + let st: State = this.currentState; + if (!st) { + st = {page: {}}; + } + st.page.size = this.pageSize; + st.page.from = 0; + st.page.to = this.pageSize - 1; + this.clrLoad(st); + } + saveSignatures(event: { [key: string]: string[] }): void { + Object.assign(this.signedCon, event); + } + + deleteRepos(repoLists: RepositoryItem[]) { + if (repoLists && repoLists.length) { + let repoNames: string[] = []; + let repArr: any[] = []; + + repoLists.forEach(repo => { + repoNames.push(repo.name); + + if (!this.signedCon[repo.name]) { + repArr.push(this.getTagInfo(repo.name)); + } + }); + + Promise.all(repArr).then(() => { + this.confirmationDialogSet( + 'REPOSITORY.DELETION_TITLE_REPO', + '', + repoNames.join(','), + repoLists, + 'REPOSITORY.DELETION_SUMMARY_REPO', + ConfirmationButtons.DELETE_CANCEL); + }); + } + } + + getTagInfo(repoName: string): Promise { + this.signedCon[repoName] = []; + return toPromise(this.tagService.getTags(repoName)) + .then(items => { + items.forEach((t: Tag) => { + if (t.signature !== null) { + this.signedCon[repoName].push(t.name); + } + }); + }) + .catch(error => this.errorHandler.error(error)); + } + + confirmationDialogSet(summaryTitle: string, signature: string, + repoName: string, repoLists: RepositoryItem[], + summaryKey: string, button: ConfirmationButtons): void { + this.translateService.get(summaryKey, + { + repoName: repoName, + signedImages: signature, + }) + .subscribe((res: string) => { + summaryKey = res; + let message = new ConfirmationMessage( + summaryTitle, + summaryKey, + repoName, + repoLists, + ConfirmationTargets.REPOSITORY, + button); + this.confirmationDialog.open(message); + + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 5000); + }); let hnd = setInterval(() => this.ref.markForCheck(), 100); setTimeout(() => clearInterval(hnd), 5000); - }); - } + } - containsLatestTag(repo: RepositoryItem): Promise { - return toPromise(this.tagService.getTags(repo.name)) - .then(items => { - if (items.some((t: Tag) => { return t.name === 'latest'; })) { - return true; - } else { - return false; - }; - }) - .catch(error => Promise.reject(false)); - } + containsLatestTag(repo: RepositoryItem): Promise { + return toPromise(this.tagService.getTags(repo.name)) + .then(items => { + if (items.some((t: Tag) => { + return t.name === 'latest'; + })) { + return true; + } else { + return false; + } + ; + }) + .catch(error => Promise.reject(false)); + } - provisionItemEvent(evt: any, repo: RepositoryItem): void { - evt.stopPropagation(); - let repoCopy = clone(repo); - repoCopy.name = this.registryUrl + ":443/" + repoCopy.name; - this.containsLatestTag(repo) - .then(containsLatest => { - if (containsLatest) { - this.repoProvisionEvent.emit(repoCopy); - } else { + provisionItemEvent(evt: any, repo: RepositoryItem): void { + evt.stopPropagation(); + let repoCopy = clone(repo); + repoCopy.name = this.registryUrl + ":443/" + repoCopy.name; + this.containsLatestTag(repo) + .then(containsLatest => { + if (containsLatest) { + this.repoProvisionEvent.emit(repoCopy); + } else { + this.addInfoEvent.emit(repoCopy); + } + }) + .catch(error => this.errorHandler.error(error)); + + } + + itemAddInfoEvent(evt: any, repo: RepositoryItem): void { + evt.stopPropagation(); + let repoCopy = clone(repo); + repoCopy.name = this.registryUrl + ":443/" + repoCopy.name; this.addInfoEvent.emit(repoCopy); - } - }) - .catch( error => this.errorHandler.error(error)); - - } - - itemAddInfoEvent(evt: any, repo: RepositoryItem): void { - evt.stopPropagation(); - let repoCopy = clone(repo); - repoCopy.name = this.registryUrl + ":443/" + repoCopy.name; - this.addInfoEvent.emit(repoCopy); - } - - deleteItemEvent(evt: any, item: RepositoryItem): void { - evt.stopPropagation(); - this.deleteRepos([item]); - } - - selectedChange(): void { - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 2000); - } - - refresh() { - this.doSearchRepoNames(""); - } - - loadNextPage() { - if (this.currentPage * this.pageSize >= this.totalCount) { - return; - } - this.currentPage = this.currentPage + 1; - - // Pagination - let params: RequestQueryParams = new RequestQueryParams(); - params.set("page", "" + this.currentPage); - params.set("page_size", "" + this.pageSize); - - this.loading = true; - - toPromise( - this.repositoryService.getRepositories( - this.projectId, - this.lastFilteredRepoName, - params - ) - ) - .then((repo: Repository) => { - this.totalCount = repo.metadata.xTotalCount; - this.repositoriesCopy = repo.data; - this.signedCon = {}; - // Do filtering and sorting - this.repositoriesCopy = doFiltering( - this.repositoriesCopy, - this.currentState - ); - this.repositoriesCopy = doSorting( - this.repositoriesCopy, - this.currentState - ); - this.repositories = this.repositories.concat(this.repositoriesCopy); - this.loading = false; - }) - .catch(error => { - this.loading = false; - this.errorHandler.error(error); - }); - let hnd = setInterval(() => this.ref.markForCheck(), 500); - setTimeout(() => clearInterval(hnd), 5000); - } - - clrLoad(state: State): void { - this.selectedRow = []; - // Keep it for future filtering and sorting - this.currentState = state; - - let pageNumber: number = calculatePage(state); - if (pageNumber <= 0) { - pageNumber = 1; } - // Pagination - let params: RequestQueryParams = new RequestQueryParams(); - params.set("page", "" + pageNumber); - params.set("page_size", "" + this.pageSize); - - this.loading = true; - - toPromise( - this.repositoryService.getRepositories( - this.projectId, - this.lastFilteredRepoName, - params - ) - ) - .then((repo: Repository) => { - this.totalCount = repo.metadata.xTotalCount; - this.repositories = repo.data; - - this.signedCon = {}; - // Do filtering and sorting - this.repositories = doFiltering( - this.repositories, - state - ); - this.repositories = doSorting(this.repositories, state); - - this.loading = false; - }) - .catch(error => { - this.loading = false; - this.errorHandler.error(error); - }); - - // Force refresh view - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 5000); - } - - getStateAfterDeletion(): State { - let total: number = this.totalCount - 1; - if (total <= 0) { - return null; + deleteItemEvent(evt: any, item: RepositoryItem): void { + evt.stopPropagation(); + this.deleteRepos([item]); } - let totalPages: number = Math.ceil(total / this.pageSize); - let targetPageNumber: number = this.currentPage; - - if (this.currentPage > totalPages) { - targetPageNumber = totalPages; // Should == currentPage -1 + selectedChange(): void { + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 2000); } - let st: State = this.currentState; - if (!st) { - st = { page: {} }; + refresh() { + this.doSearchRepoNames(""); } - st.page.size = this.pageSize; - st.page.from = (targetPageNumber - 1) * this.pageSize; - st.page.to = targetPageNumber * this.pageSize - 1; - return st; - } + loadNextPage() { + this.currentPage = this.currentPage + 1; + // Pagination + let params: RequestQueryParams = new RequestQueryParams(); + params.set("page", "" + this.currentPage); + params.set("page_size", "" + this.pageSize); - watchRepoClickEvt(repo: RepositoryItem) { - this.repoClickEvent.emit(repo); - } - - getImgLink(repo: RepositoryItem): string { - return "/container-image-icons?container-image=" + repo.name; - } - - getRepoDescrition(repo: RepositoryItem): string { - if (repo && repo.description) { - return repo.description; + this.loading = true; + toPromise( + this.repositoryService.getRepositories( + this.projectId, + this.lastFilteredRepoName, + params + ) + ) + .then((repo: Repository) => { + this.totalCount = repo.metadata.xTotalCount; + this.repositoriesCopy = repo.data; + this.signedCon = {}; + // Do filtering and sorting + this.repositoriesCopy = doFiltering( + this.repositoriesCopy, + this.currentState + ); + this.repositoriesCopy = doSorting( + this.repositoriesCopy, + this.currentState + ); + this.repositories = this.repositories.concat(this.repositoriesCopy); + this.loading = false; + }) + .catch(error => { + this.loading = false; + this.errorHandler.error(error); + }); + let hnd = setInterval(() => this.ref.markForCheck(), 500); + setTimeout(() => clearInterval(hnd), 5000); } - return "No description for this repo. You can add it to this repository."; - } - showCard(cardView: boolean) { - if (this.isCardView === cardView) { - return; - } - this.isCardView = cardView; - this.refresh(); - } - mouseEnter(itemName: string) { - if (itemName === "card") { - this.cardHover = true; - } else { - this.listHover = true; - } - } + clrLoad(state: State): void { + this.selectedRow = []; + // Keep it for future filtering and sorting + this.currentState = state; - mouseLeave(itemName: string) { - if (itemName === "card") { - this.cardHover = false; - } else { - this.listHover = false; - } - } + let pageNumber: number = calculatePage(state); + if (pageNumber <= 0) { + pageNumber = 1; + } - isHovering(itemName: string) { - if (itemName === "card") { - return this.cardHover; - } else { - return this.listHover; + // Pagination + let params: RequestQueryParams = new RequestQueryParams(); + params.set("page", "" + pageNumber); + params.set("page_size", "" + this.pageSize); + + this.loading = true; + + toPromise( + this.repositoryService.getRepositories( + this.projectId, + this.lastFilteredRepoName, + params + ) + ) + .then((repo: Repository) => { + this.totalCount = repo.metadata.xTotalCount; + this.repositories = repo.data; + + this.signedCon = {}; + // Do filtering and sorting + this.repositories = doFiltering( + this.repositories, + state + ); + this.repositories = doSorting(this.repositories, state); + + this.loading = false; + }) + .catch(error => { + this.loading = false; + this.errorHandler.error(error); + }); + + // Force refresh view + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 5000); + } + + getStateAfterDeletion(): State { + let total: number = this.totalCount - 1; + if (total <= 0) { + return null; + } + + let totalPages: number = Math.ceil(total / this.pageSize); + let targetPageNumber: number = this.currentPage; + + if (this.currentPage > totalPages) { + targetPageNumber = totalPages; // Should == currentPage -1 + } + + let st: State = this.currentState; + if (!st) { + st = {page: {}}; + } + st.page.size = this.pageSize; + st.page.from = (targetPageNumber - 1) * this.pageSize; + st.page.to = targetPageNumber * this.pageSize - 1; + + return st; + } + + watchRepoClickEvt(repo: RepositoryItem) { + this.repoClickEvent.emit(repo); + } + + getImgLink(repo: RepositoryItem): string { + return "/container-image-icons?container-image=" + repo.name; + } + + getRepoDescrition(repo: RepositoryItem): string { + if (repo && repo.description) { + return repo.description; + } + return "No description for this repo. You can add it to this repository."; + } + + showCard(cardView: boolean) { + if (this.isCardView === cardView) { + return; + } + this.isCardView = cardView; + this.refresh(); + } + + mouseEnter(itemName: string) { + if (itemName === "card") { + this.cardHover = true; + } else { + this.listHover = true; + } + } + + mouseLeave(itemName: string) { + if (itemName === "card") { + this.cardHover = false; + } else { + this.listHover = false; + } + } + + isHovering(itemName: string) { + if (itemName === "card") { + return this.cardHover; + } else { + return this.listHover; + } } - } } diff --git a/src/ui_ng/lib/src/repository/repository.component.spec.ts b/src/ui_ng/lib/src/repository/repository.component.spec.ts index 69f1e9ef6..51644ac9d 100644 --- a/src/ui_ng/lib/src/repository/repository.component.spec.ts +++ b/src/ui_ng/lib/src/repository/repository.component.spec.ts @@ -25,6 +25,7 @@ import { TagService, TagDefaultService } from '../service/tag.service'; import { ChannelService } from '../channel/index'; import {LabelPieceComponent} from "../label-piece/label-piece.component"; import {LabelDefaultService, LabelService} from "../service/label.service"; +import {OperationService} from "../operation/operation.service"; class RouterStub { @@ -174,6 +175,7 @@ describe('RepositoryComponent (inline template)', () => { { provide: TagService, useClass: TagDefaultService }, { provide: LabelService, useClass: LabelDefaultService}, { provide: ChannelService}, + { provide: OperationService } ] }); })); diff --git a/src/ui_ng/lib/src/tag/tag.component.html b/src/ui_ng/lib/src/tag/tag.component.html index 0b20205df..d588ee365 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html +++ b/src/ui_ng/lib/src/tag/tag.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.ts b/src/ui_ng/src/app/project/list-project/list-project.component.ts index fb9bef76a..f920680a8 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.ts +++ b/src/ui_ng/src/app/project/list-project/list-project.component.ts @@ -34,9 +34,9 @@ import { StatisticHandler } from "../../shared/statictics/statistic-handler.serv import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service"; import { MessageHandlerService } from "../../shared/message-handler/message-handler.service"; import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message"; -import {BatchInfo, BathInfoChanges} from "../../shared/confirmation-dialog/confirmation-batch-message"; import { SearchTriggerService } from "../../base/global-search/search-trigger.service"; import {AppConfigService} from "../../app-config.service"; +import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui"; import { Project } from "../project"; import { ProjectService } from "../project.service"; @@ -52,7 +52,6 @@ export class ListProjectComponent implements OnDestroy { filteredType = 0; // All projects searchKeyword = ""; selectedRow: Project[] = []; - batchDelectionInfos: BatchInfo[] = []; @Output() addProject = new EventEmitter(); @@ -77,6 +76,7 @@ export class ListProjectComponent implements OnDestroy { private statisticHandler: StatisticHandler, private translate: TranslateService, private deletionDialogService: ConfirmationDialogService, + private operationService: OperationService, private ref: ChangeDetectorRef) { this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => { if (message && @@ -214,15 +214,10 @@ export class ListProjectComponent implements OnDestroy { deleteProjects(p: Project[]) { let nameArr: string[] = []; - this.batchDelectionInfos = []; if (p && p.length) { p.forEach(data => { nameArr.push(data.name); - let initBatchMessage = new BatchInfo (); - initBatchMessage.name = data.name; - this.batchDelectionInfos.push(initBatchMessage); }); - this.deletionDialogService.addBatchInfoList(this.batchDelectionInfos); let deletionMessage = new ConfirmationMessage( "PROJECT.DELETION_TITLE", "PROJECT.DELETION_SUMMARY", @@ -238,7 +233,7 @@ export class ListProjectComponent implements OnDestroy { let observableLists: any[] = []; if (projects && projects.length) { projects.forEach(data => { - observableLists.push(this.delOperate(data.project_id, data.name)); + observableLists.push(this.delOperate(data)); }); Promise.all(observableLists).then(item => { let st: State = this.getStateAfterDeletion(); @@ -253,24 +248,31 @@ export class ListProjectComponent implements OnDestroy { } } - delOperate(id: number, name: string) { - let findedList = this.batchDelectionInfos.find(list => list.name === name); - return this.proService.deleteProject(id) + delOperate(project: Project) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_PROJECT'; + operMessage.data.id = project.project_id; + operMessage.state = OperationState.progressing; + operMessage.data.name = project.name; + this.operationService.publishInfo(operMessage); + + return this.proService.deleteProject(project.project_id) .then( () => { this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { - findedList = BathInfoChanges(findedList, res); + operateChanges(operMessage, OperationState.success); }); }, error => { if (error && error.status === 412) { Observable.forkJoin(this.translate.get("BATCH.DELETED_FAILURE"), this.translate.get("PROJECT.FAILED_TO_DELETE_PROJECT")).subscribe(res => { - findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); + operateChanges(operMessage, OperationState.failure, res[1]); }); } else { this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); + operateChanges(operMessage, OperationState.failure, res); }); } }); diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html index 267ce2d90..6b75222f5 100644 --- a/src/ui_ng/src/app/project/member/member.component.html +++ b/src/ui_ng/src/app/project/member/member.component.html @@ -34,8 +34,7 @@ {{m.entity_name}} - Loading... - {{roleInfo[m.role_id] | translate}} + {{roleInfo[m.role_id] | translate}} diff --git a/src/ui_ng/src/app/project/member/member.component.ts b/src/ui_ng/src/app/project/member/member.component.ts index 68305e8ac..77b26bbe8 100644 --- a/src/ui_ng/src/app/project/member/member.component.ts +++ b/src/ui_ng/src/app/project/member/member.component.ts @@ -37,7 +37,7 @@ import { Subscription } from "rxjs/Subscription"; import { Project } from "../../project/project"; import {TranslateService} from "@ngx-translate/core"; -import {BatchInfo, BathInfoChanges} from "../../shared/confirmation-dialog/confirmation-batch-message"; +import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui"; @Component({ templateUrl: "member.component.html", @@ -62,8 +62,6 @@ export class MemberComponent implements OnInit, OnDestroy { roleNum: number; isDelete = false; isChangeRole = false; - batchActionInfos: BatchInfo[] = []; - batchDeletionInfos: BatchInfo[] = []; constructor( private route: ActivatedRoute, @@ -73,6 +71,7 @@ export class MemberComponent implements OnInit, OnDestroy { private messageHandlerService: MessageHandlerService, private OperateDialogService: ConfirmationDialogService, private session: SessionService, + private operationService: OperationService, private ref: ChangeDetectorRef) { this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => { @@ -146,15 +145,6 @@ export class MemberComponent implements OnInit, OnDestroy { this.isDelete = false; this.isChangeRole = true; this.roleNum = roleId; - let nameArr: string[] = []; - this.batchActionInfos = []; - m.forEach(data => { - nameArr.push(data.entity_name); - let initBatchMessage = new BatchInfo(); - initBatchMessage.name = data.entity_name; - this.batchActionInfos.push(initBatchMessage); - }); - this.changeOpe(m); } } @@ -163,15 +153,7 @@ export class MemberComponent implements OnInit, OnDestroy { if (members && members.length) { let promiseList: any[] = []; members.forEach(member => { - if (member.entity_id === this.currentUser.user_id) { - let foundMember = this.batchActionInfos.find(batchInfo => batchInfo.name === member.entity_name); - this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => { - this.messageHandlerService.handleError(res + ": " + foundMember.name); - foundMember = BathInfoChanges(foundMember, res, false, true); - }); - } else { - promiseList.push(this.changeOperate(this.projectId, member.id, this.roleNum, member.entity_name)); - } + promiseList.push(this.changeOperate(this.projectId, this.roleNum, member)); }); Promise.all(promiseList).then(num => { @@ -181,47 +163,45 @@ export class MemberComponent implements OnInit, OnDestroy { } } - changeOperate(projectId: number, memberId: number, roleId: number, username: string) { - let foundMember = this.batchActionInfos.find(batchInfo => batchInfo.name === username); + changeOperate(projectId: number, roleId: number, member: Member) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.SWITCH_ROLE'; + operMessage.data.id = member.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = member.entity_name; + this.operationService.publishInfo(operMessage); + + if (member.entity_id === this.currentUser.user_id) { + this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + return null; + } return this.memberService - .changeMemberRole(projectId, memberId, roleId) + .changeMemberRole(projectId, member.id, roleId) .then( response => { this.translate.get("BATCH.SWITCH_SUCCESS").subscribe(res => { - foundMember = BathInfoChanges(foundMember, res); + operateChanges(operMessage, OperationState.success); }); }, error => { this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => { - this.messageHandlerService.handleError(res + ": " + username); - foundMember = BathInfoChanges(foundMember, res, false, true); + operateChanges(operMessage, OperationState.failure, res); }); } ); } - ChangeRoleOngoing(username: string) { - if (this.batchActionInfos) { - let memberActionInfo = this.batchActionInfos.find(batchInfo => batchInfo.name === username); - return memberActionInfo && memberActionInfo.status === "pending"; - } else { - return false; - } - } - deleteMembers(m: Member[]) { this.isDelete = true; this.isChangeRole = false; let nameArr: string[] = []; - this.batchDeletionInfos = []; if (m && m.length) { m.forEach(data => { nameArr.push(data.entity_name); - let initBatchMessage = new BatchInfo (); - initBatchMessage.name = data.entity_name; - this.batchDeletionInfos.push(initBatchMessage); }); - this.OperateDialogService.addBatchInfoList(this.batchDeletionInfos); let deletionMessage = new ConfirmationMessage( "MEMBER.DELETION_TITLE", @@ -239,15 +219,7 @@ export class MemberComponent implements OnInit, OnDestroy { if (members && members.length) { let promiseLists: any[] = []; members.forEach(member => { - if (member.entity_id === this.currentUser.user_id) { - let findedList = this.batchDeletionInfos.find(data => data.name === member.entity_name); - this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); - }); - } else { - promiseLists.push(this.delOperate(this.projectId, member.id, member.entity_name)); - } - + promiseLists.push(this.delOperate(this.projectId, member)); }); Promise.all(promiseLists).then(item => { @@ -257,19 +229,33 @@ export class MemberComponent implements OnInit, OnDestroy { } } - delOperate(projectId: number, memberId: number, username: string) { - let findedList = this.batchDeletionInfos.find(data => data.name === username); + delOperate(projectId: number, member: Member) { + // init operation info + let operMessage = new OperateInfo(); + operMessage.name = 'OPERATION.DELETE_MEMBER'; + operMessage.data.id = member.id; + operMessage.state = OperationState.progressing; + operMessage.data.name = member.entity_name; + this.operationService.publishInfo(operMessage); + + if (member.entity_id === this.currentUser.user_id) { + this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + return null; + } + return this.memberService - .deleteMember(projectId, memberId) + .deleteMember(projectId, member.id) .then( response => { this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { - findedList = BathInfoChanges(findedList, res); + operateChanges(operMessage, OperationState.success); }); }, error => { this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { - findedList = BathInfoChanges(findedList, res, false, true); + operateChanges(operMessage, OperationState.failure, res); }); } ); diff --git a/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-batch-message.ts b/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-batch-message.ts deleted file mode 100644 index 42b7cc24d..000000000 --- a/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-batch-message.ts +++ /dev/null @@ -1,27 +0,0 @@ - -/** - * Created by pengf on 11/22/2017. - */ - -export class BatchInfo { - name: string; - status: string; - loading: boolean; - errorState: boolean; - errorInfo: string; - constructor() { - this.status = "pending"; - this.loading = false; - this.errorState = false; - this.errorInfo = ""; - } -} - -export function BathInfoChanges(list: BatchInfo, status: string, loading = false, errStatus = false, errorInfo = '') { - list.status = status; - list.loading = loading; - list.errorState = errStatus; - list.errorInfo = errorInfo; - return list; -} - diff --git a/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-dialog.component.html b/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-dialog.component.html index cd01de777..629ece399 100644 --- a/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-dialog.component.html +++ b/src/ui_ng/src/app/shared/confirmation-dialog/confirmation-dialog.component.html @@ -5,18 +5,6 @@
{{dialogContent}}
-
-
    -
  • -   {{info.name}} - {{info.status}} - - {{info.status}}
    - {{info.errorInfo}} -
    -
  • -
-