Add async task progress and delete dialog task progress #4371

norm code
This commit is contained in:
pfh 2018-05-21 19:05:38 +08:00
parent c2c667c6d6
commit 1121c8a76b
48 changed files with 1680 additions and 1449 deletions

View File

@ -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 }))
]),
]);

View File

@ -0,0 +1,2 @@
export * from './fade-in.animation';
export * from './slide-in-out.animation';

View File

@ -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)'
}))
])
]);

View File

@ -5,19 +5,6 @@
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon> <clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
</div> </div>
<div class="confirmation-content">{{dialogContent}}</div> <div class="confirmation-content">{{dialogContent}}</div>
<div>
<ul class="batchInfoUl">
<li *ngFor="let info of batchInfors">
<span><i class="spinner spinner-inline spinner-pos" [hidden]='!info.loading'></i>&nbsp;&nbsp;{{info.name}}</span>
<span *ngIf="!info.errorInfo.length" [style.color]="colorChange(info)">{{info.status}}</span>
<span *ngIf="info.errorInfo.length" [style.color]="colorChange(info)">
<a (click)="toggleErrorTitle(errorInfo)">{{info.status}}</a>
<br>
<i #errorInfo style="display: none;">{{info.errorInfo}}</i>
</span>
</li>
</ul>
</div>
</div> </div>
<div class="modal-footer" [ngSwitch]="buttons"> <div class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0"> <ng-template [ngSwitchCase]="0">
@ -30,16 +17,14 @@
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="2"> <ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button> <button type="button" class="btn btn-danger" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="3"> <ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button> <button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="4"> <ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="operate()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button> <button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template> </ng-template>
</div> </div>
</clr-modal> </clr-modal>

View File

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { TranslateService } from '@ngx-translate/core';
import { ConfirmationMessage } from './confirmation-message'; import { ConfirmationMessage } from './confirmation-message';
@ -35,7 +35,6 @@ export class ConfirmationDialogComponent {
@Output() confirmAction = new EventEmitter<ConfirmationAcknowledgement>(); @Output() confirmAction = new EventEmitter<ConfirmationAcknowledgement>();
@Output() cancelAction = new EventEmitter<ConfirmationAcknowledgement>(); @Output() cancelAction = new EventEmitter<ConfirmationAcknowledgement>();
@Input() batchInfors: BatchInfo[] = [];
isDelete = false; isDelete = false;
constructor( constructor(
@ -52,12 +51,6 @@ export class ConfirmationDialogComponent {
this.opened = true; this.opened = true;
} }
get batchOverStatus(): boolean {
if (this.batchInfors.length) {
return this.batchInfors.every(item => item.loading === false);
}
return false;
}
colorChange(list: BatchInfo) { colorChange(list: BatchInfo) {
if (!list.loading && !list.errorState) { if (!list.loading && !list.errorState) {
@ -74,7 +67,6 @@ export class ConfirmationDialogComponent {
} }
close(): void { close(): void {
this.batchInfors = [];
this.opened = false; this.opened = false;
} }
@ -96,27 +88,6 @@ export class ConfirmationDialogComponent {
this.close(); 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 { confirm(): void {
if (!this.message) {// Inproper condition if (!this.message) {// Inproper condition
this.close(); this.close();

View File

@ -37,6 +37,7 @@ import {
ProjectService ProjectService
} from "../service/project.service"; } from "../service/project.service";
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component"; import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
import { OperationService } from "../operation/operation.service";
describe("CreateEditRuleComponent (inline template)", () => { describe("CreateEditRuleComponent (inline template)", () => {
let mockRules: ReplicationRule[] = [ let mockRules: ReplicationRule[] = [
@ -246,7 +247,8 @@ describe("CreateEditRuleComponent (inline template)", () => {
{ provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService }, { provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService } { provide: JobLogService, useClass: JobLogDefaultService },
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -38,6 +38,6 @@
</clr-datagrid> </clr-datagrid>
</div> </div>
</div> </div>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog> <confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
<hbr-create-edit-endpoint (reload)="reload($event)"></hbr-create-edit-endpoint> <hbr-create-edit-endpoint (reload)="reload($event)"></hbr-create-edit-endpoint>
</div> </div>

View File

@ -16,6 +16,7 @@ import {
EndpointDefaultService EndpointDefaultService
} from "../service/endpoint.service"; } from "../service/endpoint.service";
import { IServiceConfig, SERVICE_CONFIG } from "../service.config"; import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
import { OperationService } from "../operation/operation.service";
import { click } from "../utils"; import { click } from "../utils";
@ -94,7 +95,8 @@ describe("EndpointComponent (inline template)", () => {
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: EndpointService, useClass: EndpointDefaultService } { provide: EndpointService, useClass: EndpointDefaultService },
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -30,9 +30,9 @@ import { EndpointService } from "../service/endpoint.service";
import { ErrorHandler } from "../error-handler/index"; import { ErrorHandler } from "../error-handler/index";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import { import {
ConfirmationTargets, ConfirmationTargets,
@ -43,11 +43,8 @@ import {
import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component"; import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component";
import { toPromise, CustomComparator } from "../utils"; import { toPromise, CustomComparator } from "../utils";
import { import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
BatchInfo, import {OperationService} from "../operation/operation.service";
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
@Component({ @Component({
selector: "hbr-endpoint", selector: "hbr-endpoint",
@ -77,7 +74,6 @@ export class EndpointComponent implements OnInit, OnDestroy {
timerHandler: any; timerHandler: any;
selectedRow: Endpoint[] = []; selectedRow: Endpoint[] = [];
batchDelectionInfos: BatchInfo[] = [];
get initEndpoint(): Endpoint { get initEndpoint(): Endpoint {
return { return {
@ -90,12 +86,11 @@ export class EndpointComponent implements OnInit, OnDestroy {
}; };
} }
constructor( constructor(private endpointService: EndpointService,
private endpointService: EndpointService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef private operationService: OperationService,
) { private ref: ChangeDetectorRef) {
this.forceRefreshView(1000); this.forceRefreshView(1000);
} }
@ -109,6 +104,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
} }
selectedChange(): void { selectedChange(): void {
this.forceRefreshView(5000); this.forceRefreshView(5000);
} }
@ -162,37 +158,31 @@ export class EndpointComponent implements OnInit, OnDestroy {
deleteTargets(targets: Endpoint[]) { deleteTargets(targets: Endpoint[]) {
if (targets && targets.length) { if (targets && targets.length) {
let targetNames: string[] = []; let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => { targets.forEach(target => {
targetNames.push(target.name); targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
}); });
let deletionMessage = new ConfirmationMessage( let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE_TARGET", 'REPLICATION.DELETION_TITLE_TARGET',
"REPLICATION.DELETION_SUMMARY_TARGET", 'REPLICATION.DELETION_SUMMARY_TARGET',
targetNames.join(", ") || "", targetNames.join(', ') || '',
targets, targets,
ConfirmationTargets.TARGET, ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL ConfirmationButtons.DELETE_CANCEL);
);
this.confirmationDialogComponent.open(deletionMessage); this.confirmationDialogComponent.open(deletionMessage);
} }
} }
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if ( if (message &&
message &&
message.source === ConfirmationTargets.TARGET && message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED message.state === ConfirmationState.CONFIRMED) {
) {
let targetLists: Endpoint[] = message.data; let targetLists: Endpoint[] = message.data;
if (targetLists && targetLists.length) { if (targetLists && targetLists.length) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
targetLists.forEach(target => { targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name)); promiseLists.push(this.delOperate(target));
}); });
Promise.all(promiseLists).then(item => { Promise.all(promiseLists).then((item) => {
this.selectedRow = []; this.selectedRow = [];
this.reload(true); this.reload(true);
this.forceRefreshView(2000); this.forceRefreshView(2000);
@ -201,37 +191,38 @@ export class EndpointComponent implements OnInit, OnDestroy {
} }
} }
delOperate(id: number | string, name: string) { delOperate(target: Endpoint) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); // init operation info
return toPromise<number>(this.endpointService.deleteEndpoint(id)) let operMessage = new OperateInfo();
.then(response => { operMessage.name = 'OPERATION.DELETE_REGISTRY';
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { operMessage.data.id = target.id;
findedList = BathInfoChanges(findedList, res); operMessage.state = OperationState.progressing;
operMessage.data.name = target.name;
this.operationService.publishInfo(operMessage);
return toPromise<number>(this.endpointService
.deleteEndpoint(target.id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
operateChanges(operMessage, OperationState.success);
}); });
}) }).catch(
.catch(error => { error => {
if (error && error.status === 412) { if (error && error.status === 412) {
Observable.forkJoin( Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get("BATCH.DELETED_FAILURE"), this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => {
this.translateService.get( operateChanges(operMessage, OperationState.failure, res[1]);
"DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED"
)
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
} else { } else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
} }
}); });
} }
// Forcely refresh the view // Forcely refresh the view
forceRefreshView(duration: number): void { forceRefreshView(duration: number): void {
// Reset timer // Reset timer

View File

@ -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 { PROJECT_POLICY_CONFIG_DIRECTIVES } from './project-policy-config/index';
import { HBR_GRIDVIEW_DIRECTIVES } from './gridview/index'; import { HBR_GRIDVIEW_DIRECTIVES } from './gridview/index';
import { REPOSITORY_GRIDVIEW_DIRECTIVES } from './repository-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 { import {
SystemInfoService, SystemInfoService,
@ -47,7 +51,7 @@ import {
ProjectService, ProjectService,
ProjectDefaultService, ProjectDefaultService,
LabelService, LabelService,
LabelDefaultService LabelDefaultService,
} from './service/index'; } from './service/index';
import { import {
ErrorHandler, ErrorHandler,
@ -59,9 +63,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { TranslateServiceInitializer } from './i18n/index'; import { TranslateServiceInitializer } from './i18n/index';
import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils'; import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils';
import { ChannelService } from './channel/index'; import { ChannelService } from './channel/index';
import {LABEL_DIRECTIVES} from "./label/index"; import { OperationService } from './operation/operation.service';
import {CREATE_EDIT_LABEL_DIRECTIVES} from "./create-edit-label/index";
import {LABEL_PIECE_DIRECTIVES} from "./label-piece/index";
/** /**
* Declare default service configuration; all the endpoints will be defined in * Declare default service configuration; all the endpoints will be defined in
@ -182,6 +184,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
LABEL_PIECE_DIRECTIVES, LABEL_PIECE_DIRECTIVES,
HBR_GRIDVIEW_DIRECTIVES, HBR_GRIDVIEW_DIRECTIVES,
REPOSITORY_GRIDVIEW_DIRECTIVES, REPOSITORY_GRIDVIEW_DIRECTIVES,
OPERATION_DIRECTIVES
], ],
exports: [ exports: [
LOG_DIRECTIVES, LOG_DIRECTIVES,
@ -207,6 +210,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
LABEL_PIECE_DIRECTIVES, LABEL_PIECE_DIRECTIVES,
HBR_GRIDVIEW_DIRECTIVES, HBR_GRIDVIEW_DIRECTIVES,
REPOSITORY_GRIDVIEW_DIRECTIVES, REPOSITORY_GRIDVIEW_DIRECTIVES,
OPERATION_DIRECTIVES
], ],
providers: [] providers: []
}) })
@ -237,7 +241,8 @@ export class HarborLibraryModule {
deps: [TranslateServiceInitializer, SERVICE_CONFIG], deps: [TranslateServiceInitializer, SERVICE_CONFIG],
multi: true multi: true
}, },
ChannelService ChannelService,
OperationService
] ]
}; };
} }
@ -259,7 +264,8 @@ export class HarborLibraryModule {
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService }, config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService }, config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService },
config.labelService || {provide: LabelService, useClass: LabelDefaultService}, config.labelService || {provide: LabelService, useClass: LabelDefaultService},
ChannelService ChannelService,
OperationService
] ]
}; };
} }

View File

@ -24,3 +24,5 @@ export * from './label/index';
export * from './create-edit-label/index'; export * from './create-edit-label/index';
export * from './gridview/index'; export * from './gridview/index';
export * from './repository-gridview/index'; export * from './repository-gridview/index';
export * from './operation/index';
export * from './_animations/index';

View File

@ -37,5 +37,6 @@
</clr-datagrid> </clr-datagrid>
</div> </div>
</div> </div>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog> <confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</div> </div>

View File

@ -12,7 +12,7 @@ import {InlineAlertComponent} from "../inline-alert/inline-alert.component";
import {ErrorHandler} from "../error-handler/error-handler"; import {ErrorHandler} from "../error-handler/error-handler";
import {IServiceConfig, SERVICE_CONFIG} from "../service.config"; import {IServiceConfig, SERVICE_CONFIG} from "../service.config";
import { OperationService } from "../operation/operation.service";
describe('LabelComponent (inline template)', () => { describe('LabelComponent (inline template)', () => {
@ -79,7 +79,8 @@ describe('LabelComponent (inline template)', () => {
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{provide: LabelService, useClass: LabelDefaultService} {provide: LabelService, useClass: LabelDefaultService},
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -19,24 +19,23 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Input Input
} from "@angular/core"; } from "@angular/core";
import { Label } from "../service/interface"; import {Label} from "../service/interface";
import { LabelService } from "../service/label.service"; import {LabelService} from "../service/label.service";
import { toPromise } from "../utils"; import {toPromise} from "../utils";
import { ErrorHandler } from "../error-handler/error-handler"; import {ErrorHandler} from "../error-handler/error-handler";
import { CreateEditLabelComponent } from "../create-edit-label/create-edit-label.component"; import {CreateEditLabelComponent} from "../create-edit-label/create-edit-label.component";
import { import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { import {
ConfirmationButtons, ConfirmationButtons,
ConfirmationState, ConfirmationState,
ConfirmationTargets ConfirmationTargets
} from "../shared/shared.const"; } from "../shared/shared.const";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import { TranslateService } from "@ngx-translate/core"; import {TranslateService} from "@ngx-translate/core";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
@Component({ @Component({
selector: "hbr-label", selector: "hbr-label",
templateUrl: "./label.component.html", templateUrl: "./label.component.html",
@ -48,9 +47,7 @@ export class LabelComponent implements OnInit {
loading: boolean; loading: boolean;
targets: Label[]; targets: Label[];
targetName: string; targetName: string;
selectedRow: Label[] = []; selectedRow: Label[] = [];
batchDelectionInfos: BatchInfo[] = [];
@Input() scope: string; @Input() scope: string;
@Input() projectId = 0; @Input() projectId = 0;
@ -60,12 +57,13 @@ export class LabelComponent implements OnInit {
createEditLabel: CreateEditLabelComponent; createEditLabel: CreateEditLabelComponent;
@ViewChild("confirmationDialog") @ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent; confirmationDialogComponent: ConfirmationDialogComponent;
constructor(
private labelService: LabelService, constructor(private labelService: LabelService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef private operationService: OperationService,
) {} private ref: ChangeDetectorRef) {
}
ngOnInit(): void { ngOnInit(): void {
this.retrieve(this.scope); this.retrieve(this.scope);
@ -114,38 +112,31 @@ export class LabelComponent implements OnInit {
deleteLabels(targets: Label[]): void { deleteLabels(targets: Label[]): void {
if (targets && targets.length) { if (targets && targets.length) {
let targetNames: string[] = []; let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => { targets.forEach(target => {
targetNames.push(target.name); targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
}); });
let deletionMessage = new ConfirmationMessage( let deletionMessage = new ConfirmationMessage(
"LABEL.DELETION_TITLE_TARGET", 'LABEL.DELETION_TITLE_TARGET',
"LABEL.DELETION_SUMMARY_TARGET", 'LABEL.DELETION_SUMMARY_TARGET',
targetNames.join(", ") || "", targetNames.join(', ') || '',
targets, targets,
ConfirmationTargets.TARGET, ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL ConfirmationButtons.DELETE_CANCEL);
);
this.confirmationDialogComponent.open(deletionMessage); this.confirmationDialogComponent.open(deletionMessage);
} }
} }
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if ( if (message &&
message &&
message.source === ConfirmationTargets.TARGET && message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED message.state === ConfirmationState.CONFIRMED) {
) {
let targetLists: Label[] = message.data; let targetLists: Label[] = message.data;
if (targetLists && targetLists.length) { if (targetLists && targetLists.length) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
targetLists.forEach(target => { targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name)); promiseLists.push(this.delOperate(target));
}); });
Promise.all(promiseLists).then(item => { Promise.all(promiseLists).then((item) => {
this.selectedRow = []; this.selectedRow = [];
this.retrieve(this.scope); this.retrieve(this.scope);
}); });
@ -153,17 +144,27 @@ export class LabelComponent implements OnInit {
} }
} }
delOperate(id: number, name: string) { delOperate(target: Label) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); // init operation info
return toPromise<number>(this.labelService.deleteLabel(id)) let operMessage = new OperateInfo();
.then(response => { operMessage.name = 'OPERATION.DELETE_LABEL';
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { operMessage.data.id = target.id;
findedList = BathInfoChanges(findedList, res); operMessage.state = OperationState.progressing;
operMessage.data.name = target.name;
this.operationService.publishInfo(operMessage);
return toPromise<number>(this.labelService
.deleteLabel(target.id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
operateChanges(operMessage, OperationState.success);
}); });
}) }).catch(
.catch(error => { error => {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
}); });
} }

View File

@ -34,5 +34,5 @@
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination> <clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
<confirmation-dialog #deletionConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="deletionConfirm($event)"></confirmation-dialog> <confirmation-dialog #deletionConfirmDialog (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
</div> </div>

View File

@ -13,6 +13,7 @@ import { ReplicationRule } from '../service/interface';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import { OperationService } from "../operation/operation.service";
describe('ListReplicationRuleComponent (inline template)', () => { describe('ListReplicationRuleComponent (inline template)', () => {
@ -124,7 +125,8 @@ describe('ListReplicationRuleComponent (inline template)', () => {
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService } { provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -29,26 +29,25 @@ import "rxjs/add/observable/forkJoin";
import { Comparator } from "clarity-angular"; import { Comparator } from "clarity-angular";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { ReplicationService } from "../service/replication.service"; import {ReplicationService} from "../service/replication.service";
import { import {
ReplicationJob, ReplicationJob,
ReplicationJobItem, ReplicationJobItem,
ReplicationRule ReplicationRule
} from "../service/interface"; } from "../service/interface";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import { import {
ConfirmationState, ConfirmationState,
ConfirmationTargets, ConfirmationTargets,
ConfirmationButtons ConfirmationButtons
} from "../shared/shared.const"; } from "../shared/shared.const";
import { ErrorHandler } from "../error-handler/error-handler"; import {ErrorHandler} from "../error-handler/error-handler";
import { toPromise, CustomComparator } from "../utils"; import {toPromise, CustomComparator} from "../utils";
import { import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
BatchInfo, import {OperationService} from "../operation/operation.service";
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
@Component({ @Component({
selector: "hbr-list-replication-rule", selector: "hbr-list-replication-rule",
@ -83,7 +82,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
canDeleteRule: boolean; canDeleteRule: boolean;
selectedRow: ReplicationRule; selectedRow: ReplicationRule;
batchDelectionInfos: BatchInfo[] = [];
@ViewChild("toggleConfirmDialog") @ViewChild("toggleConfirmDialog")
toggleConfirmDialog: ConfirmationDialogComponent; toggleConfirmDialog: ConfirmationDialogComponent;
@ -91,19 +89,14 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
@ViewChild("deletionConfirmDialog") @ViewChild("deletionConfirmDialog")
deletionConfirmDialog: ConfirmationDialogComponent; deletionConfirmDialog: ConfirmationDialogComponent;
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator< startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>("start_time", "date");
ReplicationRule enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>("enabled", "number");
>("start_time", "date");
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<
ReplicationRule
>("enabled", "number");
constructor( constructor(private replicationService: ReplicationService,
private replicationService: ReplicationService,
private translateService: TranslateService, private translateService: TranslateService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private ref: ChangeDetectorRef private operationService: OperationService,
) { private ref: ChangeDetectorRef) {
setInterval(() => ref.markForCheck(), 500); setInterval(() => ref.markForCheck(), 500);
} }
@ -211,10 +204,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
deleteRule(rule: ReplicationRule) { deleteRule(rule: ReplicationRule) {
if (rule) { if (rule) {
this.batchDelectionInfos = [];
let initBatchMessage = new BatchInfo();
initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage);
let deletionMessage = new ConfirmationMessage( let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE", "REPLICATION.DELETION_TITLE",
"REPLICATION.DELETION_SUMMARY", "REPLICATION.DELETION_SUMMARY",
@ -226,29 +215,12 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
this.deletionConfirmDialog.open(deletionMessage); this.deletionConfirmDialog.open(deletionMessage);
} }
} }
deleteOpe(rule: ReplicationRule) { deleteOpe(rule: ReplicationRule) {
if (rule) { if (rule) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
Promise.all([this.jobList(rule.id)]).then(items => { Promise.all([this.jobList(rule.id)]).then(items => {
if (!this.canDeleteRule) { promiseLists.push(this.delOperate(rule));
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));
}
Promise.all(promiseLists).then(item => { Promise.all(promiseLists).then(item => {
this.selectedRow = null; this.selectedRow = null;
@ -260,33 +232,38 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
} }
} }
delOperate(ruleId: number, name: string) { delOperate(rule: ReplicationRule) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); // init operation info
return toPromise<any>(this.replicationService.deleteReplicationRule(ruleId)) 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) {
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;
}
return toPromise<any>(this.replicationService
.deleteReplicationRule(+rule.id))
.then(() => { .then(() => {
this.translateService this.translateService.get('BATCH.DELETED_SUCCESS')
.get("BATCH.DELETED_SUCCESS") .subscribe(res => operateChanges(operMessage, OperationState.success));
.subscribe(res => (findedList = BathInfoChanges(findedList, res)));
}) })
.catch(error => { .catch(error => {
if (error && error.status === 412) { if (error && error.status === 412) {
Observable.forkJoin( Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get("BATCH.DELETED_FAILURE"), this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => {
this.translateService.get( operateChanges(operMessage, OperationState.failure, res[1]);
"REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED"
)
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
} else { } else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
} }
}); });

View File

@ -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<any>[] = [
OperationComponent
];

View File

@ -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'
};

View File

@ -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;
}

View File

@ -0,0 +1,64 @@
<div class="operDiv" [@SlideInOutAnimation]="animationState">
<a class="toolBar" (click)="slideOut()">{{'OPERATION.EVENT_LOG' | translate}}<!--<clr-icon shape="angle-double" style="transform: rotate(90deg)"></clr-icon>--></a>
<div class="side-form">
<clr-icon shape="refresh" class="freshIcon" (click)="TabEvent()"></clr-icon>
<h3 class="custom-h2" style="margin-left: 34px;">{{'OPERATION.LOCAL_EVENT' | translate}}</h3>
<div style="margin-top: 10px;">
<clr-tabs>
<clr-tab>
<button clrTabLink id="link1" (click)="TabEvent()">{{'OPERATION.ALL' | translate}}</button>
<clr-tab-content id="contentAll" *clrIfActive="true">
<div class="eventInfo" *ngFor="let list of resultLists">
<div class="iconsArea">
<i class="spinner spinner-inline spinner-pos" [hidden]="list.state != 'progressing'"></i>
<clr-icon [hidden]="list.state != 'success'" size="18" shape="success-standard" style="color: green"></clr-icon>
<clr-icon [hidden]="list.state != 'failure'" size="18" shape="error-standard" style="color: red"></clr-icon>
<clr-icon [hidden]="list.state != 'interrupt'" size="18" shape="unlink" style="color: orange"></clr-icon>
</div>
<div class="infoArea">
<label class="eventName" (click)="toggleTitle(spanErrorInfo)">{{list.name | translate}}</label>
<span class="eventTarget">{{list.data.name}}</span><span class="eventTime">{{list.timeDiff | translate}}</span>
<span #spanErrorInfo class="eventErrorInf" style="display: none;">{{list.data.errorInf}}</span>
</div>
</div>
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink (click)="TabEvent()">{{'OPERATION.RUNNING' | translate}}</button>
<clr-tab-content id="contentRun" *clrIfActive>
<div class="eventInfo" *ngFor="let list of runningLists">
<div class="iconsArea">
<i class="spinner spinner-inline spinner-pos" [hidden]="list.state != 'progressing'"></i>
<clr-icon [hidden]="list.state != 'success'" size="18" shape="success-standard" style="color: green"></clr-icon>
<clr-icon [hidden]="list.state != 'failure'" size="18" shape="error-standard" style="color: red"></clr-icon>
</div>
<div class="infoArea">
<label class="eventName" (click)="toggleTitle(spanErrorInfo)">{{list.name | translate}}</label>
<span class="eventTarget">{{list.data.name}}</span><span class="eventTime">{{list.timeDiff | translate}}</span>
<span #spanErrorInfo class="eventErrorInf" style="display: none;">{{list.data.errorInf}}</span>
</div>
</div>
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink (click)="TabEvent()">{{'OPERATION.FAILED' | translate}}</button>
<clr-tab-content id="contentFailed" *clrIfActive>
<div class="eventInfo" *ngFor="let list of failLists">
<div class="iconsArea">
<i class="spinner spinner-inline spinner-pos" [hidden]="list.state != 'progressing'"></i>
<clr-icon [hidden]="list.state != 'success'" size="18" shape="success-standard" style="color: green"></clr-icon>
<clr-icon [hidden]="list.state != 'failure'" size="18" shape="error-standard" style="color: red"></clr-icon>
</div>
<div class="infoArea">
<label class="eventName" (click)="toggleTitle(spanErrorInfo)">{{list.name | translate}}</label>
<span class="eventTarget">{{list.data.name}}</span><span class="eventTime">{{list.timeDiff | translate}}</span>
<span #spanErrorInfo class="eventErrorInf" style="display: none;">{{list.data.errorInf}}</span>
</div>
</div>
</clr-tab-content>
</clr-tab>
</clr-tabs>
</div>
</div>
</div>

View File

@ -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;
});
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import {OperateInfo} from "./operate";
@Injectable()
export class OperationService {
subjects: Subject<any> = null;
operationInfoSource = new Subject<OperateInfo>();
operationInfo$ = this.operationInfoSource.asObservable();
publishInfo(data: OperateInfo): void {
this.operationInfoSource.next(data);
}
}

View File

@ -73,5 +73,6 @@
</div> </div>
<job-log-viewer #replicationLogViewer></job-log-viewer> <job-log-viewer #replicationLogViewer></job-log-viewer>
<hbr-create-edit-rule *ngIf="isSystemAdmin" [projectId]="projectId" [projectName]="projectName" (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule> <hbr-create-edit-rule *ngIf="isSystemAdmin" [projectId]="projectId" [projectName]="projectName" (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmReplication($event)"></confirmation-dialog> <confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</div> </div>

View File

@ -20,6 +20,7 @@ import { EndpointService, EndpointDefaultService } from '../service/endpoint.ser
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index'; import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
import {ProjectDefaultService, ProjectService} from "../service/project.service"; import {ProjectDefaultService, ProjectService} from "../service/project.service";
import {OperationService} from "../operation/operation.service";
describe('Replication Component (inline template)', () => { describe('Replication Component (inline template)', () => {
@ -231,7 +232,8 @@ describe('Replication Component (inline template)', () => {
{ provide: ReplicationService, useClass: ReplicationDefaultService }, { provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService }, { provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService }, { provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService } { provide: JobLogService, useClass: JobLogDefaultService },
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -56,12 +56,10 @@ import {
ConfirmationState ConfirmationState
} from "../shared/shared.const"; } from "../shared/shared.const";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; 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 { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; 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 } = [ const ruleStatus: { [key: string]: any } = [
{ key: "all", description: "REPLICATION.ALL_STATUS" }, { key: "all", description: "REPLICATION.ALL_STATUS" },
@ -130,7 +128,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
hiddenJobList = true; hiddenJobList = true;
jobs: ReplicationJobItem[]; jobs: ReplicationJobItem[];
batchDelectionInfos: BatchInfo[] = [];
toggleJobSearchOption = optionalSearch; toggleJobSearchOption = optionalSearch;
currentJobSearchOption: number; currentJobSearchOption: number;
@ -165,8 +162,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
constructor( constructor(
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private replicationService: ReplicationService, private replicationService: ReplicationService,
private translateService: TranslateService private operationService: OperationService,
) {} private translateService: TranslateService) {}
public get showPaginationIndex(): boolean { public get showPaginationIndex(): boolean {
return this.totalCount > 0; return this.totalCount > 0;
@ -307,10 +304,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
replicateManualRule(rule: ReplicationRule) { replicateManualRule(rule: ReplicationRule) {
if (rule) { if (rule) {
this.batchDelectionInfos = [];
let initBatchMessage = new BatchInfo();
initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage);
let replicationMessage = new ConfirmationMessage( let replicationMessage = new ConfirmationMessage(
"REPLICATION.REPLICATION_TITLE", "REPLICATION.REPLICATION_TITLE",
"REPLICATION.REPLICATION_SUMMARY", "REPLICATION.REPLICATION_SUMMARY",
@ -332,43 +325,37 @@ export class ReplicationComponent implements OnInit, OnDestroy {
let rule: ReplicationRule = message.data; let rule: ReplicationRule = message.data;
if (rule) { if (rule) {
Promise.all([this.replicationOperate(+rule.id, rule.name)]).then( Promise.all([this.replicationOperate(rule)]).then((item) => {
item => {
this.selectOneRule(rule); this.selectOneRule(rule);
} });
);
} }
} }
} }
replicationOperate(ruleId: number, name: string) { replicationOperate(rule: ReplicationRule) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); // 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<any>(this.replicationService.replicateRule(ruleId)) return toPromise<any>(this.replicationService.replicateRule(+rule.id))
.then(response => { .then(response => {
this.translateService this.translateService.get('BATCH.REPLICATE_SUCCESS')
.get("BATCH.REPLICATE_SUCCESS") .subscribe(res => operateChanges(operMessage, OperationState.success));
.subscribe(res => (findedList = BathInfoChanges(findedList, res)));
}) })
.catch(error => { .catch(error => {
if (error && error.status === 412) { if (error && error.status === 412) {
Observable.forkJoin( Observable.forkJoin(this.translateService.get('BATCH.REPLICATE_FAILURE'),
this.translateService.get("BATCH.REPLICATE_FAILURE"), this.translateService.get('REPLICATION.REPLICATE_SUMMARY_FAILURE'))
this.translateService.get("REPLICATION.REPLICATE_SUMMARY_FAILURE") .subscribe(function (res) {
).subscribe(function(res) { operateChanges(operMessage, OperationState.failure, res[1]);
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
} else { } else {
this.translateService this.translateService.get('BATCH.REPLICATE_FAILURE').subscribe(res => {
.get("BATCH.REPLICATE_FAILURE") operateChanges(operMessage, OperationState.failure, res);
.subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
}); });
} }
}); });

View File

@ -17,6 +17,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div *ngIf="!isCardView" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div *ngIf="!isCardView" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()"> <clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
<clr-dg-action-bar> <clr-dg-action-bar>
@ -44,6 +45,8 @@
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div> </div>
</div>
<hbr-gridview *ngIf="isCardView" #gridView style="position:relative;" [items]="repositories" [loading]="loading" [pageSize]="pageSize" <hbr-gridview *ngIf="isCardView" #gridView style="position:relative;" [items]="repositories" [loading]="loading" [pageSize]="pageSize"
[currentPage]="currentPage" [totalCount]="totalCount" [expectScrollPercent]="90" [withAdmiral]="withAdmiral" (loadNextPageEvent)="loadNextPage()"> [currentPage]="currentPage" [totalCount]="totalCount" [expectScrollPercent]="90" [withAdmiral]="withAdmiral" (loadNextPageEvent)="loadNextPage()">
<ng-template let-item="item"> <ng-template let-item="item">
@ -90,5 +93,5 @@
</a> </a>
</ng-template> </ng-template>
</hbr-gridview> </hbr-gridview>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog> <confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</div> </div>

View File

@ -22,6 +22,7 @@ import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index'; import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
import { JobLogViewerComponent } from '../job-log-viewer/index'; import { JobLogViewerComponent } from '../job-log-viewer/index';
import {LabelPieceComponent} from "../label-piece/label-piece.component"; import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {OperationService} from "../operation/operation.service";
describe('RepositoryComponentGridview (inline template)', () => { describe('RepositoryComponentGridview (inline template)', () => {
@ -115,7 +116,8 @@ describe('RepositoryComponentGridview (inline template)', () => {
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useClass: RepositoryDefaultService }, { provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: TagService, useClass: TagDefaultService }, { provide: TagService, useClass: TagDefaultService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService } { provide: SystemInfoService, useClass: SystemInfoDefaultService },
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -10,11 +10,11 @@ import {
OnChanges, OnChanges,
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import { Router } from "@angular/router"; import {Router} from "@angular/router";
import { Observable } from "rxjs/Observable"; import {Observable} from "rxjs/Observable";
import "rxjs/add/observable/forkJoin"; import "rxjs/add/observable/forkJoin";
import { TranslateService } from "@ngx-translate/core"; import {TranslateService} from "@ngx-translate/core";
import { Comparator, State } from "clarity-angular"; import {Comparator, State} from "clarity-angular";
import { import {
Repository, Repository,
@ -24,31 +24,17 @@ import {
RequestQueryParams, RequestQueryParams,
RepositoryItem, RepositoryItem,
TagService TagService
} from "../service/index"; } from '../service/index';
import { ErrorHandler } from "../error-handler/error-handler"; import {ErrorHandler} from '../error-handler/error-handler';
import { import {toPromise, CustomComparator, DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting, clone} from '../utils';
toPromise, import {ConfirmationState, ConfirmationTargets, ConfirmationButtons} from '../shared/shared.const';
CustomComparator, import {ConfirmationDialogComponent} from '../confirmation-dialog/confirmation-dialog.component';
DEFAULT_PAGE_SIZE, import {ConfirmationMessage} from '../confirmation-dialog/confirmation-message';
calculatePage, import {ConfirmationAcknowledgement} from '../confirmation-dialog/confirmation-state-message';
doFiltering, import {Tag} from '../service/interface';
doSorting, import {GridViewComponent} from '../gridview/grid-view.component';
clone import {OperationService} from "../operation/operation.service";
} from "../utils"; import {OperateInfo, OperationState, operateChanges} from "../operation/operate";
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";
@Component({ @Component({
selector: "hbr-repository-gridview", selector: "hbr-repository-gridview",
@ -79,13 +65,8 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
cardHover = false; cardHover = false;
listHover = false; listHover = false;
batchDelectionInfos: BatchInfo[] = []; pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number');
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator< tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number');
RepositoryItem
>("pull_count", "number");
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<
RepositoryItem
>("tags_count", "number");
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
currentPage = 1; currentPage = 1;
@ -97,15 +78,15 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
@ViewChild("gridView") gridView: GridViewComponent; @ViewChild("gridView") gridView: GridViewComponent;
constructor( constructor(private errorHandler: ErrorHandler,
private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private repositoryService: RepositoryService, private repositoryService: RepositoryService,
private systemInfoService: SystemInfoService, private systemInfoService: SystemInfoService,
private tagService: TagService, private tagService: TagService,
private operationService: OperationService,
private ref: ChangeDetectorRef, private ref: ChangeDetectorRef,
private router: Router private router: Router) {
) {} }
public get registryUrl(): string { public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : ""; return this.systemInfo ? this.systemInfo.registry_url : "";
@ -153,19 +134,18 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if ( if (message &&
message &&
message.source === ConfirmationTargets.REPOSITORY && message.source === ConfirmationTargets.REPOSITORY &&
message.state === ConfirmationState.CONFIRMED message.state === ConfirmationState.CONFIRMED) {
) {
let promiseLists: any[] = [];
let repoNames: string[] = message.data.split(",");
repoNames.forEach(repoName => { let repoLists = message.data;
promiseLists.push(this.delOperate(repoName)); if (repoLists && repoLists.length) {
let promiseLists: any[] = [];
repoLists.forEach(repo => {
promiseLists.push(this.delOperate(repo));
}); });
Promise.all(promiseLists).then(item => { Promise.all(promiseLists).then((item) => {
this.selectedRow = []; this.selectedRow = [];
this.refresh(); this.refresh();
let st: State = this.getStateAfterDeletion(); let st: State = this.getStateAfterDeletion();
@ -177,60 +157,47 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
}); });
} }
} }
}
delOperate(repoName: string) { delOperate(repo: RepositoryItem) {
let findedList = this.batchDelectionInfos.find( // init operation info
data => data.name === repoName let operMessage = new OperateInfo();
); operMessage.name = 'OPERATION.DELETE_REPO';
if (this.signedCon[repoName].length !== 0) { operMessage.data.id = repo.id;
Observable.forkJoin( operMessage.state = OperationState.progressing;
this.translateService.get("BATCH.DELETED_FAILURE"), operMessage.data.name = repo.name;
this.translateService.get("REPOSITORY.DELETION_TITLE_REPO_SIGNED") this.operationService.publishInfo(operMessage);
).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); 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 { } else {
return toPromise<number>( return toPromise<number>(this.repositoryService
this.repositoryService.deleteRepository(repoName) .deleteRepository(repo.name))
) .then(
.then(response => { response => {
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => { this.translateService.get('BATCH.DELETED_SUCCESS').subscribe(res => {
findedList = BathInfoChanges(findedList, res); operateChanges(operMessage, OperationState.success);
}); });
}) }).catch(error => {
.catch(error => {
if (error.status === "412") { if (error.status === "412") {
Observable.forkJoin( Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get("BATCH.DELETED_FAILURE"), this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => {
this.translateService.get("REPOSITORY.TAGS_SIGNED") operateChanges(operMessage, OperationState.failure, res[1]);
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
return; return;
} }
if (error.status === 503) { if (error.status === 503) {
Observable.forkJoin( Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get("BATCH.DELETED_FAILURE"), this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => {
this.translateService.get("REPOSITORY.TAGS_NO_DELETE") operateChanges(operMessage, OperationState.failure, res[1]);
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
return; return;
} }
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
}); });
} }
@ -241,7 +208,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.currentPage = 1; this.currentPage = 1;
let st: State = this.currentState; let st: State = this.currentState;
if (!st) { if (!st) {
st = { page: {} }; st = {page: {}};
} }
st.page.size = this.pageSize; st.page.size = this.pageSize;
st.page.from = 0; st.page.from = 0;
@ -256,14 +223,10 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
deleteRepos(repoLists: RepositoryItem[]) { deleteRepos(repoLists: RepositoryItem[]) {
if (repoLists && repoLists.length) { if (repoLists && repoLists.length) {
let repoNames: string[] = []; let repoNames: string[] = [];
this.batchDelectionInfos = [];
let repArr: any[] = []; let repArr: any[] = [];
repoLists.forEach(repo => { repoLists.forEach(repo => {
repoNames.push(repo.name); repoNames.push(repo.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = repo.name;
this.batchDelectionInfos.push(initBatchMessage);
if (!this.signedCon[repo.name]) { if (!this.signedCon[repo.name]) {
repArr.push(this.getTagInfo(repo.name)); repArr.push(this.getTagInfo(repo.name));
@ -272,12 +235,12 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
Promise.all(repArr).then(() => { Promise.all(repArr).then(() => {
this.confirmationDialogSet( this.confirmationDialogSet(
"REPOSITORY.DELETION_TITLE_REPO", 'REPOSITORY.DELETION_TITLE_REPO',
"", '',
repoNames.join(","), repoNames.join(','),
"REPOSITORY.DELETION_SUMMARY_REPO", repoLists,
ConfirmationButtons.DELETE_CANCEL 'REPOSITORY.DELETION_SUMMARY_REPO',
); ConfirmationButtons.DELETE_CANCEL);
}); });
} }
} }
@ -295,39 +258,13 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
.catch(error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
} }
signedDataSet(repoName: string): void { confirmationDialogSet(summaryTitle: string, signature: string,
let signature = ""; repoName: string, repoLists: RepositoryItem[],
if (this.signedCon[repoName].length === 0) { summaryKey: string, button: ConfirmationButtons): void {
this.confirmationDialogSet( this.translateService.get(summaryKey,
"REPOSITORY.DELETION_TITLE_REPO", {
signature,
repoName,
"REPOSITORY.DELETION_SUMMARY_REPO",
ConfirmationButtons.DELETE_CANCEL
);
return;
}
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, repoName: repoName,
signedImages: signature signedImages: signature,
}) })
.subscribe((res: string) => { .subscribe((res: string) => {
summaryKey = res; summaryKey = res;
@ -335,25 +272,29 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
summaryTitle, summaryTitle,
summaryKey, summaryKey,
repoName, repoName,
repoName, repoLists,
ConfirmationTargets.REPOSITORY, ConfirmationTargets.REPOSITORY,
button button);
);
this.confirmationDialog.open(message); this.confirmationDialog.open(message);
let hnd = setInterval(() => this.ref.markForCheck(), 100); let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000); setTimeout(() => clearInterval(hnd), 5000);
}); });
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000);
} }
containsLatestTag(repo: RepositoryItem): Promise<boolean> { containsLatestTag(repo: RepositoryItem): Promise<boolean> {
return toPromise<Tag[]>(this.tagService.getTags(repo.name)) return toPromise<Tag[]>(this.tagService.getTags(repo.name))
.then(items => { .then(items => {
if (items.some((t: Tag) => { return t.name === 'latest'; })) { if (items.some((t: Tag) => {
return t.name === 'latest';
})) {
return true; return true;
} else { } else {
return false; return false;
}; }
;
}) })
.catch(error => Promise.reject(false)); .catch(error => Promise.reject(false));
} }
@ -370,7 +311,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.addInfoEvent.emit(repoCopy); this.addInfoEvent.emit(repoCopy);
} }
}) })
.catch( error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
} }
@ -396,18 +337,13 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
loadNextPage() { loadNextPage() {
if (this.currentPage * this.pageSize >= this.totalCount) {
return;
}
this.currentPage = this.currentPage + 1; this.currentPage = this.currentPage + 1;
// Pagination // Pagination
let params: RequestQueryParams = new RequestQueryParams(); let params: RequestQueryParams = new RequestQueryParams();
params.set("page", "" + this.currentPage); params.set("page", "" + this.currentPage);
params.set("page_size", "" + this.pageSize); params.set("page_size", "" + this.pageSize);
this.loading = true; this.loading = true;
toPromise<Repository>( toPromise<Repository>(
this.repositoryService.getRepositories( this.repositoryService.getRepositories(
this.projectId, this.projectId,
@ -502,7 +438,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
let st: State = this.currentState; let st: State = this.currentState;
if (!st) { if (!st) {
st = { page: {} }; st = {page: {}};
} }
st.page.size = this.pageSize; st.page.size = this.pageSize;
st.page.from = (targetPageNumber - 1) * this.pageSize; st.page.from = (targetPageNumber - 1) * this.pageSize;
@ -525,6 +461,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
return "No description for this repo. You can add it to this repository."; return "No description for this repo. You can add it to this repository.";
} }
showCard(cardView: boolean) { showCard(cardView: boolean) {
if (this.isCardView === cardView) { if (this.isCardView === cardView) {
return; return;

View File

@ -25,6 +25,7 @@ import { TagService, TagDefaultService } from '../service/tag.service';
import { ChannelService } from '../channel/index'; import { ChannelService } from '../channel/index';
import {LabelPieceComponent} from "../label-piece/label-piece.component"; import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {LabelDefaultService, LabelService} from "../service/label.service"; import {LabelDefaultService, LabelService} from "../service/label.service";
import {OperationService} from "../operation/operation.service";
class RouterStub { class RouterStub {
@ -174,6 +175,7 @@ describe('RepositoryComponent (inline template)', () => {
{ provide: TagService, useClass: TagDefaultService }, { provide: TagService, useClass: TagDefaultService },
{ provide: LabelService, useClass: LabelDefaultService}, { provide: LabelService, useClass: LabelDefaultService},
{ provide: ChannelService}, { provide: ChannelService},
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -1,4 +1,4 @@
<confirmation-dialog class="hidden-tag" #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog> <confirmation-dialog class="hidden-tag" #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
<clr-modal class="hidden-tag" [(clrModalOpen)]="showTagManifestOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable"> <clr-modal class="hidden-tag" [(clrModalOpen)]="showTagManifestOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3> <h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
<div class="modal-body"> <div class="modal-body">

View File

@ -18,6 +18,7 @@ import { JobLogViewerComponent } from '../job-log-viewer/index';
import {CopyInputComponent} from "../push-image/copy-input.component"; import {CopyInputComponent} from "../push-image/copy-input.component";
import {LabelPieceComponent} from "../label-piece/label-piece.component"; import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {LabelDefaultService, LabelService} from "../service/label.service"; import {LabelDefaultService, LabelService} from "../service/label.service";
import {OperationService} from "../operation/operation.service";
describe('TagComponent (inline template)', () => { describe('TagComponent (inline template)', () => {
@ -112,7 +113,8 @@ describe('TagComponent (inline template)', () => {
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: TagService, useClass: TagDefaultService }, { provide: TagService, useClass: TagDefaultService },
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }, { provide: ScanningResultService, useClass: ScanningResultDefaultService },
{provide: LabelService, useClass: LabelDefaultService} {provide: LabelService, useClass: LabelDefaultService},
{ provide: OperationService }
] ]
}); });
})); }));

View File

@ -53,11 +53,10 @@ import {
clone, clone,
} from "../utils"; } from "../utils";
import {CopyInputComponent} from "../push-image/copy-input.component"; import {CopyInputComponent} from "../push-image/copy-input.component";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
import {LabelService} from "../service/label.service"; import {LabelService} from "../service/label.service";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
export interface LabelState { export interface LabelState {
iconsShow: boolean; iconsShow: boolean;
@ -97,7 +96,6 @@ export class TagComponent implements OnInit, AfterViewInit {
staticBackdrop = true; staticBackdrop = true;
closable = false; closable = false;
lastFilteredTagName: string; lastFilteredTagName: string;
batchDelectionInfos: BatchInfo[] = [];
inprogress: boolean; inprogress: boolean;
openLabelFilterPanel: boolean; openLabelFilterPanel: boolean;
openLabelFilterPiece: boolean; openLabelFilterPiece: boolean;
@ -146,6 +144,7 @@ export class TagComponent implements OnInit, AfterViewInit {
private labelService: LabelService, private labelService: LabelService,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef, private ref: ChangeDetectorRef,
private operationService: OperationService,
private channel: ChannelService private channel: ChannelService
) { } ) { }
@ -566,12 +565,8 @@ export class TagComponent implements OnInit, AfterViewInit {
deleteTags(tags: Tag[]) { deleteTags(tags: Tag[]) {
if (tags && tags.length) { if (tags && tags.length) {
let tagNames: string[] = []; let tagNames: string[] = [];
this.batchDelectionInfos = [];
tags.forEach(tag => { tags.forEach(tag => {
tagNames.push(tag.name); tagNames.push(tag.name);
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = tag.name;
this.batchDelectionInfos.push(initBatchMessage);
}); });
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons; let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
@ -598,7 +593,7 @@ export class TagComponent implements OnInit, AfterViewInit {
if (tags && tags.length) { if (tags && tags.length) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
tags.forEach(tag => { tags.forEach(tag => {
promiseLists.push(this.delOperate(tag.signature, tag.name)); promiseLists.push(this.delOperate(tag));
}); });
Promise.all(promiseLists).then((item) => { Promise.all(promiseLists).then((item) => {
@ -609,34 +604,43 @@ export class TagComponent implements OnInit, AfterViewInit {
} }
} }
delOperate(signature: any, name: string) { delOperate(tag: Tag) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); // init operation info
if (signature) { let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_TAG';
operMessage.data.id = tag.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = tag.name;
this.operationService.publishInfo(operMessage);
if (tag.signature) {
Observable.forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"), Observable.forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => { this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => {
let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + ":4443 -d ~/.docker/trust remove -p " + let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl +
this.registryUrl + "/" + this.repoName + " " + name; ":4443 -d ~/.docker/trust remove -p " +
findedList = BathInfoChanges(findedList, res[0], false, true, wrongInfo); this.registryUrl + "/" + this.repoName +
" " + name;
operateChanges(operMessage, OperationState.failure, wrongInfo);
}); });
} else { } else {
return toPromise<number>(this.tagService return toPromise<number>(this.tagService
.deleteTag(this.repoName, name)) .deleteTag(this.repoName, tag.name))
.then( .then(
response => { response => {
this.translateService.get("BATCH.DELETED_SUCCESS") this.translateService.get("BATCH.DELETED_SUCCESS")
.subscribe(res => { .subscribe(res => {
findedList = BathInfoChanges(findedList, res); operateChanges(operMessage, OperationState.success);
}); });
}).catch(error => { }).catch(error => {
if (error.status === 503) { if (error.status === 503) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); operateChanges(operMessage, OperationState.failure, res[1]);
}); });
return; return;
} }
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
}); });
} }

View File

@ -32,7 +32,6 @@
"clarity-icons": "^0.10.27", "clarity-icons": "^0.10.27",
"clarity-ui": "^0.10.27", "clarity-ui": "^0.10.27",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"harbor-ui": "0.7.19-dev.8",
"intl": "^1.2.5", "intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2", "mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0", "ngx-cookie": "^1.0.0",
@ -50,6 +49,7 @@
"bootstrap": "4.0.0-alpha.5", "bootstrap": "4.0.0-alpha.5",
"codelyzer": "~2.0.0-beta.4", "codelyzer": "~2.0.0-beta.4",
"enhanced-resolve": "^3.0.0", "enhanced-resolve": "^3.0.0",
"harbor-ui": "0.7.19-test-3",
"jasmine-core": "2.4.1", "jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0", "jasmine-spec-reporter": "2.5.0",
"karma": "~1.7.0", "karma": "~1.7.0",

View File

@ -49,6 +49,7 @@
</clr-vertical-nav-group-children> </clr-vertical-nav-group-children>
</clr-vertical-nav-group> </clr-vertical-nav-group>
</clr-vertical-nav> </clr-vertical-nav>
<hbr-operation-model *ngIf="isUserExisting"></hbr-operation-model>
</div> </div>
</clr-main-container> </clr-main-container>
<account-settings-modal></account-settings-modal> <account-settings-modal></account-settings-modal>

View File

@ -34,9 +34,9 @@ import { StatisticHandler } from "../../shared/statictics/statistic-handler.serv
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service"; import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service"; import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message"; 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 { SearchTriggerService } from "../../base/global-search/search-trigger.service";
import {AppConfigService} from "../../app-config.service"; import {AppConfigService} from "../../app-config.service";
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
import { Project } from "../project"; import { Project } from "../project";
import { ProjectService } from "../project.service"; import { ProjectService } from "../project.service";
@ -52,7 +52,6 @@ export class ListProjectComponent implements OnDestroy {
filteredType = 0; // All projects filteredType = 0; // All projects
searchKeyword = ""; searchKeyword = "";
selectedRow: Project[] = []; selectedRow: Project[] = [];
batchDelectionInfos: BatchInfo[] = [];
@Output() addProject = new EventEmitter<void>(); @Output() addProject = new EventEmitter<void>();
@ -77,6 +76,7 @@ export class ListProjectComponent implements OnDestroy {
private statisticHandler: StatisticHandler, private statisticHandler: StatisticHandler,
private translate: TranslateService, private translate: TranslateService,
private deletionDialogService: ConfirmationDialogService, private deletionDialogService: ConfirmationDialogService,
private operationService: OperationService,
private ref: ChangeDetectorRef) { private ref: ChangeDetectorRef) {
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => { this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
if (message && if (message &&
@ -214,15 +214,10 @@ export class ListProjectComponent implements OnDestroy {
deleteProjects(p: Project[]) { deleteProjects(p: Project[]) {
let nameArr: string[] = []; let nameArr: string[] = [];
this.batchDelectionInfos = [];
if (p && p.length) { if (p && p.length) {
p.forEach(data => { p.forEach(data => {
nameArr.push(data.name); 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( let deletionMessage = new ConfirmationMessage(
"PROJECT.DELETION_TITLE", "PROJECT.DELETION_TITLE",
"PROJECT.DELETION_SUMMARY", "PROJECT.DELETION_SUMMARY",
@ -238,7 +233,7 @@ export class ListProjectComponent implements OnDestroy {
let observableLists: any[] = []; let observableLists: any[] = [];
if (projects && projects.length) { if (projects && projects.length) {
projects.forEach(data => { projects.forEach(data => {
observableLists.push(this.delOperate(data.project_id, data.name)); observableLists.push(this.delOperate(data));
}); });
Promise.all(observableLists).then(item => { Promise.all(observableLists).then(item => {
let st: State = this.getStateAfterDeletion(); let st: State = this.getStateAfterDeletion();
@ -253,24 +248,31 @@ export class ListProjectComponent implements OnDestroy {
} }
} }
delOperate(id: number, name: string) { delOperate(project: Project) {
let findedList = this.batchDelectionInfos.find(list => list.name === name); // init operation info
return this.proService.deleteProject(id) 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( .then(
() => { () => {
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res); operateChanges(operMessage, OperationState.success);
}); });
}, },
error => { error => {
if (error && error.status === 412) { if (error && error.status === 412) {
Observable.forkJoin(this.translate.get("BATCH.DELETED_FAILURE"), Observable.forkJoin(this.translate.get("BATCH.DELETED_FAILURE"),
this.translate.get("PROJECT.FAILED_TO_DELETE_PROJECT")).subscribe(res => { 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 { } else {
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
} }
}); });

View File

@ -34,8 +34,7 @@
<clr-dg-row *clrDgItems="let m of members" [clrDgItem]="m"> <clr-dg-row *clrDgItems="let m of members" [clrDgItem]="m">
<clr-dg-cell>{{m.entity_name}}</clr-dg-cell> <clr-dg-cell>{{m.entity_name}}</clr-dg-cell>
<clr-dg-cell> <clr-dg-cell>
<span *ngIf="ChangeRoleOngoing(m.entity_name)" class="spinner spinner-inline"> Loading... </span> <span>{{roleInfo[m.role_id] | translate}}</span>
<span *ngIf="!ChangeRoleOngoing(m.entity_name)">{{roleInfo[m.role_id] | translate}}</span>
</clr-dg-cell> </clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>

View File

@ -37,7 +37,7 @@ import { Subscription } from "rxjs/Subscription";
import { Project } from "../../project/project"; import { Project } from "../../project/project";
import {TranslateService} from "@ngx-translate/core"; 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({ @Component({
templateUrl: "member.component.html", templateUrl: "member.component.html",
@ -62,8 +62,6 @@ export class MemberComponent implements OnInit, OnDestroy {
roleNum: number; roleNum: number;
isDelete = false; isDelete = false;
isChangeRole = false; isChangeRole = false;
batchActionInfos: BatchInfo[] = [];
batchDeletionInfos: BatchInfo[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -73,6 +71,7 @@ export class MemberComponent implements OnInit, OnDestroy {
private messageHandlerService: MessageHandlerService, private messageHandlerService: MessageHandlerService,
private OperateDialogService: ConfirmationDialogService, private OperateDialogService: ConfirmationDialogService,
private session: SessionService, private session: SessionService,
private operationService: OperationService,
private ref: ChangeDetectorRef) { private ref: ChangeDetectorRef) {
this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => { this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => {
@ -146,15 +145,6 @@ export class MemberComponent implements OnInit, OnDestroy {
this.isDelete = false; this.isDelete = false;
this.isChangeRole = true; this.isChangeRole = true;
this.roleNum = roleId; 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); this.changeOpe(m);
} }
} }
@ -163,15 +153,7 @@ export class MemberComponent implements OnInit, OnDestroy {
if (members && members.length) { if (members && members.length) {
let promiseList: any[] = []; let promiseList: any[] = [];
members.forEach(member => { members.forEach(member => {
if (member.entity_id === this.currentUser.user_id) { promiseList.push(this.changeOperate(this.projectId, this.roleNum, member));
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));
}
}); });
Promise.all(promiseList).then(num => { Promise.all(promiseList).then(num => {
@ -181,47 +163,45 @@ export class MemberComponent implements OnInit, OnDestroy {
} }
} }
changeOperate(projectId: number, memberId: number, roleId: number, username: string) { changeOperate(projectId: number, roleId: number, member: Member) {
let foundMember = this.batchActionInfos.find(batchInfo => batchInfo.name === username); // 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 return this.memberService
.changeMemberRole(projectId, memberId, roleId) .changeMemberRole(projectId, member.id, roleId)
.then( .then(
response => { response => {
this.translate.get("BATCH.SWITCH_SUCCESS").subscribe(res => { this.translate.get("BATCH.SWITCH_SUCCESS").subscribe(res => {
foundMember = BathInfoChanges(foundMember, res); operateChanges(operMessage, OperationState.success);
}); });
}, },
error => { error => {
this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => { this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => {
this.messageHandlerService.handleError(res + ": " + username); operateChanges(operMessage, OperationState.failure, res);
foundMember = BathInfoChanges(foundMember, res, false, true);
}); });
} }
); );
} }
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[]) { deleteMembers(m: Member[]) {
this.isDelete = true; this.isDelete = true;
this.isChangeRole = false; this.isChangeRole = false;
let nameArr: string[] = []; let nameArr: string[] = [];
this.batchDeletionInfos = [];
if (m && m.length) { if (m && m.length) {
m.forEach(data => { m.forEach(data => {
nameArr.push(data.entity_name); 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( let deletionMessage = new ConfirmationMessage(
"MEMBER.DELETION_TITLE", "MEMBER.DELETION_TITLE",
@ -239,15 +219,7 @@ export class MemberComponent implements OnInit, OnDestroy {
if (members && members.length) { if (members && members.length) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
members.forEach(member => { members.forEach(member => {
if (member.entity_id === this.currentUser.user_id) { promiseLists.push(this.delOperate(this.projectId, member));
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));
}
}); });
Promise.all(promiseLists).then(item => { Promise.all(promiseLists).then(item => {
@ -257,19 +229,33 @@ export class MemberComponent implements OnInit, OnDestroy {
} }
} }
delOperate(projectId: number, memberId: number, username: string) { delOperate(projectId: number, member: Member) {
let findedList = this.batchDeletionInfos.find(data => data.name === username); // 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 return this.memberService
.deleteMember(projectId, memberId) .deleteMember(projectId, member.id)
.then( .then(
response => { response => {
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res); operateChanges(operMessage, OperationState.success);
}); });
}, },
error => { error => {
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
} }
); );

View File

@ -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;
}

View File

@ -5,18 +5,6 @@
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon> <clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
</div> </div>
<div class="confirmation-content">{{dialogContent}}</div> <div class="confirmation-content">{{dialogContent}}</div>
<div>
<ul class="batchInfoUl">
<li *ngFor="let info of resultLists">
<span> <i class="spinner spinner-inline spinner-pos" [hidden]='!info.loading'></i>&nbsp;&nbsp;{{info.name}}</span>
<span *ngIf="!info.errorInfo.length" [style.color]="colorChange(info)">{{info.status}}</span>
<span *ngIf="info.errorInfo.length" [style.color]="colorChange(info)">
<a (click)="toggleErrorTitle(errorInfo)" >{{info.status}}</a><br>
<i #errorInfo style="display: none;">{{info.errorInfo}}</i>
</span>
</li>
</ul>
</div>
</div> </div>
<div class="modal-footer" [ngSwitch]="buttons"> <div class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0"> <ng-template [ngSwitchCase]="0">
@ -29,8 +17,7 @@
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="2"> <ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button> <button type="button" class="btn btn-danger" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="3"> <ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button> <button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>

View File

@ -19,7 +19,6 @@ import { ConfirmationDialogService } from './confirmation-dialog.service';
import { ConfirmationMessage } from './confirmation-message'; import { ConfirmationMessage } from './confirmation-message';
import { ConfirmationAcknowledgement } from './confirmation-state-message'; import { ConfirmationAcknowledgement } from './confirmation-state-message';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared.const'; import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared.const';
import {BatchInfo} from "./confirmation-batch-message";
@Component({ @Component({
selector: 'confiramtion-dialog', selector: 'confiramtion-dialog',
@ -32,32 +31,9 @@ export class ConfirmationDialogComponent implements OnDestroy {
dialogTitle: string = ""; dialogTitle: string = "";
dialogContent: string = ""; dialogContent: string = "";
message: ConfirmationMessage; message: ConfirmationMessage;
resultLists: BatchInfo[] = [];
annouceSubscription: Subscription; annouceSubscription: Subscription;
batchInfoSubscription: Subscription;
buttons: ConfirmationButtons; buttons: ConfirmationButtons;
isDelete: boolean = false; isDelete: boolean = false;
get batchOverStatus(): boolean {
if (this.resultLists.length) {
return this.resultLists.every(item => item.loading === false);
}
return false;
}
colorChange(list: BatchInfo) {
if (!list.loading && !list.errorState) {
return 'green';
} else if (!list.loading && list.errorState) {
return 'red';
} else {
return '#666';
}
}
toggleErrorTitle(errorSpan: any) {
errorSpan.style.display = (errorSpan.style.display === 'none') ? 'block' : 'none';
}
constructor( constructor(
private confirmationService: ConfirmationDialogService, private confirmationService: ConfirmationDialogService,
private translate: TranslateService) { private translate: TranslateService) {
@ -71,19 +47,12 @@ export class ConfirmationDialogComponent implements OnDestroy {
this.buttons = msg.buttons; this.buttons = msg.buttons;
this.open(); this.open();
}); });
this.batchInfoSubscription = confirmationService.confirmationBatch$.subscribe(data => {
this.resultLists = data;
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.annouceSubscription) { if (this.annouceSubscription) {
this.annouceSubscription.unsubscribe(); this.annouceSubscription.unsubscribe();
} }
if (this.batchInfoSubscription) {
this.resultLists = [];
this.batchInfoSubscription.unsubscribe();
}
} }
open(): void { open(): void {
@ -91,7 +60,6 @@ export class ConfirmationDialogComponent implements OnDestroy {
} }
close(): void { close(): void {
this.resultLists = [];
this.opened = false; this.opened = false;
} }
@ -113,26 +81,6 @@ export class ConfirmationDialogComponent implements OnDestroy {
this.close(); this.close();
} }
operate(): void {
if (!this.message) {// Improper condition
this.close();
return;
}
if (this.resultLists.length) {
this.resultLists.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;
this.confirmationService.confirm(new ConfirmationAcknowledgement(
ConfirmationState.CONFIRMED,
data,
target
));
}
confirm(): void { confirm(): void {
if (!this.message) { if (!this.message) {
// Inproper condition // Inproper condition

View File

@ -16,17 +16,14 @@ import { Subject } from 'rxjs/Subject';
import { ConfirmationMessage } from './confirmation-message'; import { ConfirmationMessage } from './confirmation-message';
import { ConfirmationAcknowledgement } from './confirmation-state-message'; import { ConfirmationAcknowledgement } from './confirmation-state-message';
import {BatchInfo} from "./confirmation-batch-message";
@Injectable() @Injectable()
export class ConfirmationDialogService { export class ConfirmationDialogService {
confirmationAnnoucedSource = new Subject<ConfirmationMessage>(); confirmationAnnoucedSource = new Subject<ConfirmationMessage>();
confirmationConfirmSource = new Subject<ConfirmationAcknowledgement>(); confirmationConfirmSource = new Subject<ConfirmationAcknowledgement>();
confirmationBatchSource = new Subject<BatchInfo[]>();
confirmationAnnouced$ = this.confirmationAnnoucedSource.asObservable(); confirmationAnnouced$ = this.confirmationAnnoucedSource.asObservable();
confirmationConfirm$ = this.confirmationConfirmSource.asObservable(); confirmationConfirm$ = this.confirmationConfirmSource.asObservable();
confirmationBatch$ = this.confirmationBatchSource.asObservable();
// User confirm the action // User confirm the action
public confirm(ack: ConfirmationAcknowledgement): void { public confirm(ack: ConfirmationAcknowledgement): void {
@ -42,7 +39,4 @@ export class ConfirmationDialogService {
public openComfirmDialog(message: ConfirmationMessage): void { public openComfirmDialog(message: ConfirmationMessage): void {
this.confirmationAnnoucedSource.next(message); this.confirmationAnnoucedSource.next(message);
} }
public addBatchInfoList(data: BatchInfo[]): void {
this.confirmationBatchSource.next(data);
}
} }

View File

@ -20,14 +20,13 @@ import { TranslateService } from '@ngx-translate/core';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service'; import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message';
import {BatchInfo, BathInfoChanges} from '../shared/confirmation-dialog/confirmation-batch-message';
import { MessageHandlerService } from '../shared/message-handler/message-handler.service'; import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
import { SessionService } from '../shared/session.service'; import { SessionService } from '../shared/session.service';
import { AppConfigService } from '../app-config.service'; import { AppConfigService } from '../app-config.service';
import { NewUserModalComponent } from './new-user-modal.component'; import { NewUserModalComponent } from './new-user-modal.component';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { User } from './user'; import { User } from './user';
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
/** /**
* NOTES: * NOTES:
* Pagination for this component is a temporary workaround solution. It will be replaced in future release. * Pagination for this component is a temporary workaround solution. It will be replaced in future release.
@ -51,7 +50,6 @@ export class UserComponent implements OnInit, OnDestroy {
originalUsers: Promise<User[]>; originalUsers: Promise<User[]>;
selectedRow: User[] = []; selectedRow: User[] = [];
ISADMNISTRATOR: string = "USER.ENABLE_ADMIN_ACTION"; ISADMNISTRATOR: string = "USER.ENABLE_ADMIN_ACTION";
batchDelectionInfos: BatchInfo[] = [];
currentTerm: string; currentTerm: string;
totalCount: number = 0; totalCount: number = 0;
@ -72,6 +70,7 @@ export class UserComponent implements OnInit, OnDestroy {
private msgHandler: MessageHandlerService, private msgHandler: MessageHandlerService,
private session: SessionService, private session: SessionService,
private appConfigService: AppConfigService, private appConfigService: AppConfigService,
private operationService: OperationService,
private ref: ChangeDetectorRef) { private ref: ChangeDetectorRef) {
this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => { this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => {
if (confirmed && if (confirmed &&
@ -228,19 +227,15 @@ export class UserComponent implements OnInit, OnDestroy {
// Delete the specified user // Delete the specified user
deleteUsers(users: User[]): void { deleteUsers(users: User[]): void {
let userArr: string[] = []; let userArr: string[] = [];
this.batchDelectionInfos = [];
if (this.onlySelf) { if (this.onlySelf) {
return; return;
} }
if (users && users.length) { if (users && users.length) {
users.forEach(user => { users.forEach(user => {
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = user.username;
this.batchDelectionInfos.push(initBatchMessage);
userArr.push(user.username); userArr.push(user.username);
}); });
this.deletionDialogService.addBatchInfoList(this.batchDelectionInfos); }
// Confirm deletion // Confirm deletion
let msg: ConfirmationMessage = new ConfirmationMessage( let msg: ConfirmationMessage = new ConfirmationMessage(
"USER.DELETION_TITLE", "USER.DELETION_TITLE",
@ -252,21 +247,12 @@ export class UserComponent implements OnInit, OnDestroy {
); );
this.deletionDialogService.openComfirmDialog(msg); this.deletionDialogService.openComfirmDialog(msg);
} }
}
delUser(users: User[]): void { delUser(users: User[]): void {
// this.batchInfoDialog.open();
let promiseLists: any[] = []; let promiseLists: any[] = [];
if (users && users.length) { if (users && users.length) {
users.forEach(user => { users.forEach(user => {
let findedList = this.batchDelectionInfos.find(data => data.name === user.username); promiseLists.push(this.delOperate(user));
if (this.isMySelf(user.user_id)) {
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
} else {
promiseLists.push(this.delOperate(user.user_id, user.username));
}
}); });
Promise.all(promiseLists).then((item) => { Promise.all(promiseLists).then((item) => {
@ -276,15 +262,31 @@ export class UserComponent implements OnInit, OnDestroy {
}); });
} }
} }
delOperate(id: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); delOperate(user: User) {
return this.userService.deleteUser(id).then(() => { // init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_USER';
operMessage.data.id = user.user_id;
operMessage.state = OperationState.progressing;
operMessage.data.name = user.username;
this.operationService.publishInfo(operMessage);
if (this.isMySelf(user.user_id)) {
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
operateChanges(operMessage, OperationState.failure, res);
});
return null;
}
return this.userService.deleteUser(user.user_id).then(() => {
this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => { this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => {
findedList = BathInfoChanges(findedList, res); operateChanges(operMessage, OperationState.success);
}); });
}).catch(error => { }).catch(error => {
this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => { this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); operateChanges(operMessage, OperationState.failure, res);
}); });
}); });
} }

View File

@ -654,6 +654,27 @@
"SATURDAY": "Saturday", "SATURDAY": "Saturday",
"SUNDAY": "Sunday" "SUNDAY": "Sunday"
}, },
"OPERATION": {
"LOCAL_EVENT": "Local Events",
"ALL": "All",
"RUNNING": "Running",
"FAILED": "Failed",
"DELETE_PROJECT": "Delete project",
"DELETE_REPO": "Delete repository",
"DELETE_TAG": "Delete tag",
"DELETE_USER": "Delete user",
"DELETE_REGISTRY": "Delete registry",
"DELETE_REPLICATION": "Delete replication",
"DELETE_MEMBER": "Delete member",
"SWITCH_ROLE": "Switch role",
"DELETE_LABEL": "Delete label",
"REPLICATION": "Replication",
"DAY_AGO": " day(s) ago",
"HOUR_AGO": " hour(s) ago",
"MINUTE_AGO": " minute(s) ago",
"SECOND_AGO": "less 1 minute",
"EVENT_LOG": "EVENT LOG"
},
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.", "UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.", "UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
"REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.", "REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.",

View File

@ -654,6 +654,27 @@
"SATURDAY": "Saturday", "SATURDAY": "Saturday",
"SUNDAY": "Sunday" "SUNDAY": "Sunday"
}, },
"OPERATION": {
"LOCAL_EVENT": "Local Events",
"ALL": "All",
"RUNNING": "Running",
"FAILED": "Failed",
"DELETE_PROJECT": "Delete project",
"DELETE_REPO": "Delete repository",
"DELETE_TAG": "Delete tag",
"DELETE_USER": "Delete user",
"DELETE_REGISTRY": "Delete registry",
"DELETE_REPLICATION": "Delete replication",
"DELETE_MEMBER": "Delete member",
"SWITCH_ROLE": "Switch role",
"DELETE_LABEL": "Delete label",
"REPLICATION": "Replication",
"DAY_AGO": " day(s) ago",
"HOUR_AGO": " hour(s) ago",
"MINUTE_AGO": " minute(s) ago",
"SECOND_AGO": "less 1 minute",
"EVENT_LOG": "EVENT LOG"
},
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.", "UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.", "UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
"REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.", "REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.",

View File

@ -612,6 +612,27 @@
"SATURDAY": "Saturday", "SATURDAY": "Saturday",
"SUNDAY": "Sunday" "SUNDAY": "Sunday"
}, },
"OPERATION": {
"LOCAL_EVENT": "Local Events",
"ALL": "All",
"RUNNING": "Running",
"FAILED": "Failed",
"DELETE_PROJECT": "Delete project",
"DELETE_REPO": "Delete repository",
"DELETE_TAG": "Delete tag",
"DELETE_USER": "Delete user",
"DELETE_REGISTRY": "Delete registry",
"DELETE_REPLICATION": "Delete replication",
"DELETE_MEMBER": "Delete member",
"SWITCH_ROLE": "Switch role",
"DELETE_LABEL": "Delete label",
"REPLICATION": "Replication",
"DAY_AGO": " day(s) ago",
"HOUR_AGO": " hour(s) ago",
"MINUTE_AGO": " minute(s) ago",
"SECOND_AGO": "less 1 minute",
"EVENT_LOG": "EVENT LOG"
},
"UNKNOWN_ERROR": "Des erreurs inconnues sont survenues. Veuillez réessayer plus tard.", "UNKNOWN_ERROR": "Des erreurs inconnues sont survenues. Veuillez réessayer plus tard.",
"UNAUTHORIZED_ERROR": "Votre session est invalide ou a expiré. Vous devez vous connecter pour continuer votre action.", "UNAUTHORIZED_ERROR": "Votre session est invalide ou a expiré. Vous devez vous connecter pour continuer votre action.",
"REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.", "REPO_READ_ONLY": "Harbor is set to read-only mode, Deleting repository, tag and pushing image will be disabled under read-only mode.",

View File

@ -654,6 +654,27 @@
"SATURDAY": "周六", "SATURDAY": "周六",
"SUNDAY": "周日" "SUNDAY": "周日"
}, },
"OPERATION": {
"LOCAL_EVENT": "本地事件",
"ALL": "所有",
"RUNNING": "进行中",
"FAILED": "失败",
"DELETE_PROJECT": "删除项目",
"DELETE_REPO": "删除仓库",
"DELETE_TAG": "删除镜像标签",
"DELETE_USER": "删除用户",
"DELETE_REGISTRY": "Delete registry",
"DELETE_REPLICATION": "删除复制",
"DELETE_MEMBER": "删除成员",
"SWITCH_ROLE": "切换角色",
"DELETE_LABEL": "删除标签",
"REPLICATION": "复制",
"DAY_AGO": "天前",
"HOUR_AGO": "小时前",
"MINUTE_AGO": "分钟前",
"SECOND_AGO": "少于一分钟",
"EVENT_LOG": "事件日志"
},
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。", "UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。", "UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
"REPO_READ_ONLY": "Harbor被设置为只读模式在此模式下不能删除仓库、标签及推送镜像。", "REPO_READ_ONLY": "Harbor被设置为只读模式在此模式下不能删除仓库、标签及推送镜像。",

View File

@ -103,7 +103,6 @@ Delete Repo
Sleep 1 Sleep 1
Click Element xpath=//clr-modal//button[2] Click Element xpath=//clr-modal//button[2]
Sleep 1 Sleep 1
Click Element xpath=//button[contains(.,"CLOSE")]
Delete Repo on CardView Delete Repo on CardView
[Arguments] ${reponame} [Arguments] ${reponame}