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>
</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 class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0">
@ -30,16 +17,14 @@
</ng-template>
<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-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<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-primary" (click)="operate()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
</ng-template>
</div>
</clr-modal>

View File

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

View File

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

View File

@ -38,6 +38,6 @@
</clr-datagrid>
</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>
</div>

View File

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

View File

@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Component,
OnInit,
OnDestroy,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef
Component,
OnInit,
OnDestroy,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef
} from "@angular/core";
import { Subscription } from "rxjs/Subscription";
import { Observable } from "rxjs/Observable";
@ -30,220 +30,211 @@ import { EndpointService } from "../service/endpoint.service";
import { ErrorHandler } from "../error-handler/index";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import {
ConfirmationTargets,
ConfirmationState,
ConfirmationButtons
ConfirmationTargets,
ConfirmationState,
ConfirmationButtons
} from "../shared/shared.const";
import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component";
import { toPromise, CustomComparator } from "../utils";
import {
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
@Component({
selector: "hbr-endpoint",
templateUrl: "./endpoint.component.html",
styleUrls: ["./endpoint.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
selector: "hbr-endpoint",
templateUrl: "./endpoint.component.html",
styleUrls: ["./endpoint.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EndpointComponent implements OnInit, OnDestroy {
@ViewChild(CreateEditEndpointComponent)
createEditEndpointComponent: CreateEditEndpointComponent;
@ViewChild(CreateEditEndpointComponent)
createEditEndpointComponent: CreateEditEndpointComponent;
@ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent;
@ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent;
targets: Endpoint[];
target: Endpoint;
targets: Endpoint[];
target: Endpoint;
targetName: string;
subscription: Subscription;
targetName: string;
subscription: Subscription;
loading: boolean = false;
loading: boolean = false;
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>(
"creation_time",
"date"
);
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>(
"creation_time",
"date"
);
timerHandler: any;
selectedRow: Endpoint[] = [];
batchDelectionInfos: BatchInfo[] = [];
timerHandler: any;
selectedRow: Endpoint[] = [];
get initEndpoint(): Endpoint {
return {
endpoint: "",
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}
constructor(
private endpointService: EndpointService,
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private ref: ChangeDetectorRef
) {
this.forceRefreshView(1000);
}
ngOnInit(): void {
this.targetName = "";
this.retrieve();
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
get initEndpoint(): Endpoint {
return {
endpoint: "",
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}
}
selectedChange(): void {
this.forceRefreshView(5000);
}
retrieve(): void {
this.loading = true;
this.selectedRow = [];
toPromise<Endpoint[]>(this.endpointService.getEndpoints(this.targetName))
.then(targets => {
this.targets = targets || [];
constructor(private endpointService: EndpointService,
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private operationService: OperationService,
private ref: ChangeDetectorRef) {
this.forceRefreshView(1000);
this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
doSearchTargets(targetName: string) {
this.targetName = targetName;
this.retrieve();
}
refreshTargets() {
this.retrieve();
}
reload($event: any) {
this.targetName = "";
this.retrieve();
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
this.target = this.initEndpoint;
}
editTargets(targets: Endpoint[]) {
if (targets && targets.length === 1) {
let target = targets[0];
let editable = true;
if (!target.id) {
return;
}
let id: number | string = target.id;
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
}
}
deleteTargets(targets: Endpoint[]) {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE_TARGET",
"REPLICATION.DELETION_SUMMARY_TARGET",
targetNames.join(", ") || "",
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL
);
this.confirmationDialogComponent.open(deletionMessage);
ngOnInit(): void {
this.targetName = "";
this.retrieve();
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (
message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED
) {
let targetLists: Endpoint[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
});
Promise.all(promiseLists).then(item => {
this.selectedRow = [];
this.reload(true);
this.forceRefreshView(2000);
});
}
}
}
delOperate(id: number | string, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<number>(this.endpointService.deleteEndpoint(id))
.then(response => {
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get(
"DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED"
)
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
} else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
selectedChange(): void {
this.forceRefreshView(5000);
}
retrieve(): void {
this.loading = true;
this.selectedRow = [];
toPromise<Endpoint[]>(this.endpointService.getEndpoints(this.targetName))
.then(targets => {
this.targets = targets || [];
this.forceRefreshView(1000);
this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
doSearchTargets(targetName: string) {
this.targetName = targetName;
this.retrieve();
}
refreshTargets() {
this.retrieve();
}
reload($event: any) {
this.targetName = "";
this.retrieve();
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
this.target = this.initEndpoint;
}
editTargets(targets: Endpoint[]) {
if (targets && targets.length === 1) {
let target = targets[0];
let editable = true;
if (!target.id) {
return;
}
let id: number | string = target.id;
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
}
}
deleteTargets(targets: Endpoint[]) {
if (targets && targets.length) {
let targetNames: string[] = [];
targets.forEach(target => {
targetNames.push(target.name);
});
let deletionMessage = new ConfirmationMessage(
'REPLICATION.DELETION_TITLE_TARGET',
'REPLICATION.DELETION_SUMMARY_TARGET',
targetNames.join(', ') || '',
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL);
this.confirmationDialogComponent.open(deletionMessage);
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let targetLists: Endpoint[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target));
});
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.reload(true);
this.forceRefreshView(2000);
});
}
}
}
delOperate(target: Endpoint) {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_REGISTRY';
operMessage.data.id = target.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = target.name;
this.operationService.publishInfo(operMessage);
return toPromise<number>(this.endpointService
.deleteEndpoint(target.id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
operateChanges(operMessage, OperationState.success);
});
}).catch(
error => {
if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => {
operateChanges(operMessage, OperationState.failure, res[1]);
});
} else {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
operateChanges(operMessage, OperationState.failure, res);
});
}
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
}

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

View File

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

View File

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

View File

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

View File

@ -12,174 +12,175 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Component,
OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
Component,
OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
} from "@angular/core";
import { Label } from "../service/interface";
import { LabelService } from "../service/label.service";
import { toPromise } from "../utils";
import { ErrorHandler } from "../error-handler/error-handler";
import { CreateEditLabelComponent } from "../create-edit-label/create-edit-label.component";
import {Label} from "../service/interface";
import {LabelService} from "../service/label.service";
import {toPromise} from "../utils";
import {ErrorHandler} from "../error-handler/error-handler";
import {CreateEditLabelComponent} from "../create-edit-label/create-edit-label.component";
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import {
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import {
ConfirmationButtons,
ConfirmationState,
ConfirmationTargets
ConfirmationButtons,
ConfirmationState,
ConfirmationTargets
} from "../shared/shared.const";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import { TranslateService } from "@ngx-translate/core";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import {TranslateService} from "@ngx-translate/core";
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
@Component({
selector: "hbr-label",
templateUrl: "./label.component.html",
styleUrls: ["./label.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
selector: "hbr-label",
templateUrl: "./label.component.html",
styleUrls: ["./label.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LabelComponent implements OnInit {
timerHandler: any;
loading: boolean;
targets: Label[];
targetName: string;
timerHandler: any;
loading: boolean;
targets: Label[];
targetName: string;
selectedRow: Label[] = [];
selectedRow: Label[] = [];
batchDelectionInfos: BatchInfo[] = [];
@Input() scope: string;
@Input() projectId = 0;
@Input() hasProjectAdminRole: boolean;
@Input() scope: string;
@Input() projectId = 0;
@Input() hasProjectAdminRole: boolean;
@ViewChild(CreateEditLabelComponent)
createEditLabel: CreateEditLabelComponent;
@ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent;
@ViewChild(CreateEditLabelComponent)
createEditLabel: CreateEditLabelComponent;
@ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent;
constructor(
private labelService: LabelService,
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private ref: ChangeDetectorRef
) {}
ngOnInit(): void {
this.retrieve(this.scope);
}
retrieve(scope: string, name = "") {
this.loading = true;
this.selectedRow = [];
this.targetName = "";
toPromise<Label[]>(this.labelService.getLabels(scope, this.projectId, name))
.then(targets => {
this.targets = targets || [];
this.loading = false;
this.forceRefreshView(2000);
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
openModal(): void {
this.createEditLabel.openModal();
}
reload(): void {
this.retrieve(this.scope);
}
doSearchTargets(targetName: string) {
this.retrieve(this.scope, targetName);
}
refreshTargets() {
this.retrieve(this.scope);
}
selectedChange(): void {
// this.forceRefreshView(5000);
}
editLabel(label: Label[]): void {
this.createEditLabel.editModel(label[0].id, label);
}
deleteLabels(targets: Label[]): void {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
"LABEL.DELETION_TITLE_TARGET",
"LABEL.DELETION_SUMMARY_TARGET",
targetNames.join(", ") || "",
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL
);
this.confirmationDialogComponent.open(deletionMessage);
constructor(private labelService: LabelService,
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private operationService: OperationService,
private ref: ChangeDetectorRef) {
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (
message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED
) {
let targetLists: Label[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
});
Promise.all(promiseLists).then(item => {
this.selectedRow = [];
this.retrieve(this.scope);
});
}
ngOnInit(): void {
this.retrieve(this.scope);
}
}
delOperate(id: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<number>(this.labelService.deleteLabel(id))
.then(response => {
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
})
.catch(error => {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
retrieve(scope: string, name = "") {
this.loading = true;
this.selectedRow = [];
this.targetName = "";
toPromise<Label[]>(this.labelService.getLabels(scope, this.projectId, name))
.then(targets => {
this.targets = targets || [];
this.loading = false;
this.forceRefreshView(2000);
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
openModal(): void {
this.createEditLabel.openModal();
}
reload(): void {
this.retrieve(this.scope);
}
doSearchTargets(targetName: string) {
this.retrieve(this.scope, targetName);
}
refreshTargets() {
this.retrieve(this.scope);
}
selectedChange(): void {
// this.forceRefreshView(5000);
}
editLabel(label: Label[]): void {
this.createEditLabel.editModel(label[0].id, label);
}
deleteLabels(targets: Label[]): void {
if (targets && targets.length) {
let targetNames: string[] = [];
targets.forEach(target => {
targetNames.push(target.name);
});
let deletionMessage = new ConfirmationMessage(
'LABEL.DELETION_TITLE_TARGET',
'LABEL.DELETION_SUMMARY_TARGET',
targetNames.join(', ') || '',
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL);
this.confirmationDialogComponent.open(deletionMessage);
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let targetLists: Label[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target));
});
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.retrieve(this.scope);
});
}
}
}
delOperate(target: Label) {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_LABEL';
operMessage.data.id = target.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = target.name;
this.operationService.publishInfo(operMessage);
return toPromise<number>(this.labelService
.deleteLabel(target.id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
operateChanges(operMessage, OperationState.success);
});
}).catch(
error => {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
operateChanges(operMessage, OperationState.failure, res);
});
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
}

View File

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

View File

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

View File

@ -12,283 +12,260 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Component,
Input,
Output,
OnInit,
EventEmitter,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnChanges,
SimpleChange,
SimpleChanges
Component,
Input,
Output,
OnInit,
EventEmitter,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnChanges,
SimpleChange,
SimpleChanges
} from "@angular/core";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/forkJoin";
import { Comparator } from "clarity-angular";
import { TranslateService } from "@ngx-translate/core";
import { ReplicationService } from "../service/replication.service";
import {ReplicationService} from "../service/replication.service";
import {
ReplicationJob,
ReplicationJobItem,
ReplicationRule
ReplicationJob,
ReplicationJobItem,
ReplicationRule
} from "../service/interface";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
import {
ConfirmationState,
ConfirmationTargets,
ConfirmationButtons
ConfirmationState,
ConfirmationTargets,
ConfirmationButtons
} from "../shared/shared.const";
import { ErrorHandler } from "../error-handler/error-handler";
import { toPromise, CustomComparator } from "../utils";
import {
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import {ErrorHandler} from "../error-handler/error-handler";
import {toPromise, CustomComparator} from "../utils";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
@Component({
selector: "hbr-list-replication-rule",
templateUrl: "./list-replication-rule.component.html",
styleUrls: ["./list-replication-rule.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
selector: "hbr-list-replication-rule",
templateUrl: "./list-replication-rule.component.html",
styleUrls: ["./list-replication-rule.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListReplicationRuleComponent implements OnInit, OnChanges {
nullTime = "0001-01-01T00:00:00Z";
nullTime = "0001-01-01T00:00:00Z";
@Input() projectId: number;
@Input() isSystemAdmin: boolean;
@Input() selectedId: number | string;
@Input() withReplicationJob: boolean;
@Input() projectId: number;
@Input() isSystemAdmin: boolean;
@Input() selectedId: number | string;
@Input() withReplicationJob: boolean;
@Input() loading = false;
@Input() loading = false;
@Output() reload = new EventEmitter<boolean>();
@Output() selectOne = new EventEmitter<ReplicationRule>();
@Output() editOne = new EventEmitter<ReplicationRule>();
@Output() toggleOne = new EventEmitter<ReplicationRule>();
@Output() hideJobs = new EventEmitter<any>();
@Output() redirect = new EventEmitter<ReplicationRule>();
@Output() openNewRule = new EventEmitter<any>();
@Output() replicateManual = new EventEmitter<ReplicationRule[]>();
@Output() reload = new EventEmitter<boolean>();
@Output() selectOne = new EventEmitter<ReplicationRule>();
@Output() editOne = new EventEmitter<ReplicationRule>();
@Output() toggleOne = new EventEmitter<ReplicationRule>();
@Output() hideJobs = new EventEmitter<any>();
@Output() redirect = new EventEmitter<ReplicationRule>();
@Output() openNewRule = new EventEmitter<any>();
@Output() replicateManual = new EventEmitter<ReplicationRule[]>();
projectScope = false;
projectScope = false;
rules: ReplicationRule[];
changedRules: ReplicationRule[];
ruleName: string;
canDeleteRule: boolean;
rules: ReplicationRule[];
changedRules: ReplicationRule[];
ruleName: string;
canDeleteRule: boolean;
selectedRow: ReplicationRule;
batchDelectionInfos: BatchInfo[] = [];
selectedRow: ReplicationRule;
@ViewChild("toggleConfirmDialog")
toggleConfirmDialog: ConfirmationDialogComponent;
@ViewChild("toggleConfirmDialog")
toggleConfirmDialog: ConfirmationDialogComponent;
@ViewChild("deletionConfirmDialog")
deletionConfirmDialog: ConfirmationDialogComponent;
@ViewChild("deletionConfirmDialog")
deletionConfirmDialog: ConfirmationDialogComponent;
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<
ReplicationRule
>("start_time", "date");
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<
ReplicationRule
>("enabled", "number");
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>("start_time", "date");
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>("enabled", "number");
constructor(
private replicationService: ReplicationService,
private translateService: TranslateService,
private errorHandler: ErrorHandler,
private ref: ChangeDetectorRef
) {
setInterval(() => ref.markForCheck(), 500);
}
trancatedDescription(desc: string): string {
if (desc.length > 35) {
return desc.substr(0, 35);
} else {
return desc;
constructor(private replicationService: ReplicationService,
private translateService: TranslateService,
private errorHandler: ErrorHandler,
private operationService: OperationService,
private ref: ChangeDetectorRef) {
setInterval(() => ref.markForCheck(), 500);
}
}
ngOnInit(): void {
// Global scope
if (!this.projectScope) {
this.retrieveRules();
}
}
ngOnChanges(changes: SimpleChanges): void {
let proIdChange: SimpleChange = changes["projectId"];
if (proIdChange) {
if (proIdChange.currentValue !== proIdChange.previousValue) {
if (proIdChange.currentValue) {
this.projectId = proIdChange.currentValue;
this.projectScope = true; // Scope is project, not global list
// Initially load the replication rule data
this.retrieveRules();
trancatedDescription(desc: string): string {
if (desc.length > 35) {
return desc.substr(0, 35);
} else {
return desc;
}
}
}
}
retrieveRules(ruleName = ""): void {
this.loading = true;
/*this.selectedRow = null;*/
toPromise<ReplicationRule[]>(
this.replicationService.getReplicationRules(this.projectId, ruleName)
)
.then(rules => {
this.rules = rules || [];
// job list hidden
this.hideJobs.emit();
this.changedRules = this.rules;
this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
replicateRule(rules: ReplicationRule[]): void {
this.replicateManual.emit(rules);
}
deletionConfirm(message: ConfirmationAcknowledgement) {
if (
message &&
message.source === ConfirmationTargets.POLICY &&
message.state === ConfirmationState.CONFIRMED
) {
this.deleteOpe(message.data);
ngOnInit(): void {
// Global scope
if (!this.projectScope) {
this.retrieveRules();
}
}
}
selectRule(rule: ReplicationRule): void {
this.selectedId = rule.id || "";
this.selectOne.emit(rule);
}
redirectTo(rule: ReplicationRule): void {
this.redirect.emit(rule);
}
openModal(): void {
this.openNewRule.emit();
}
editRule(rule: ReplicationRule) {
this.editOne.emit(rule);
}
jobList(id: string | number): Promise<void> {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;
let count = 0;
return toPromise<ReplicationJob>(this.replicationService.getJobs(id))
.then(response => {
ruleData = response.data;
if (ruleData.length) {
ruleData.forEach(job => {
if (
job.status === "pending" ||
job.status === "running" ||
job.status === "retrying"
) {
count++;
ngOnChanges(changes: SimpleChanges): void {
let proIdChange: SimpleChange = changes["projectId"];
if (proIdChange) {
if (proIdChange.currentValue !== proIdChange.previousValue) {
if (proIdChange.currentValue) {
this.projectId = proIdChange.currentValue;
this.projectScope = true; // Scope is project, not global list
// Initially load the replication rule data
this.retrieveRules();
}
}
});
}
this.canDeleteRule = count > 0 ? false : true;
})
.catch(error => this.errorHandler.error(error));
}
deleteRule(rule: ReplicationRule) {
if (rule) {
this.batchDelectionInfos = [];
let initBatchMessage = new BatchInfo();
initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage);
let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE",
"REPLICATION.DELETION_SUMMARY",
rule.name,
rule,
ConfirmationTargets.POLICY,
ConfirmationButtons.DELETE_CANCEL
);
this.deletionConfirmDialog.open(deletionMessage);
}
}
deleteOpe(rule: ReplicationRule) {
if (rule) {
let promiseLists: any[] = [];
Promise.all([this.jobList(rule.id)]).then(items => {
retrieveRules(ruleName = ""): void {
this.loading = true;
/*this.selectedRow = null;*/
toPromise<ReplicationRule[]>(
this.replicationService.getReplicationRules(this.projectId, ruleName)
)
.then(rules => {
this.rules = rules || [];
// job list hidden
this.hideJobs.emit();
this.changedRules = this.rules;
this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
replicateRule(rules: ReplicationRule[]): void {
this.replicateManual.emit(rules);
}
deletionConfirm(message: ConfirmationAcknowledgement) {
if (
message &&
message.source === ConfirmationTargets.POLICY &&
message.state === ConfirmationState.CONFIRMED
) {
this.deleteOpe(message.data);
}
}
selectRule(rule: ReplicationRule): void {
this.selectedId = rule.id || "";
this.selectOne.emit(rule);
}
redirectTo(rule: ReplicationRule): void {
this.redirect.emit(rule);
}
openModal(): void {
this.openNewRule.emit();
}
editRule(rule: ReplicationRule) {
this.editOne.emit(rule);
}
jobList(id: string | number): Promise<void> {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;
let count = 0;
return toPromise<ReplicationJob>(this.replicationService.getJobs(id))
.then(response => {
ruleData = response.data;
if (ruleData.length) {
ruleData.forEach(job => {
if (
job.status === "pending" ||
job.status === "running" ||
job.status === "retrying"
) {
count++;
}
});
}
this.canDeleteRule = count > 0 ? false : true;
})
.catch(error => this.errorHandler.error(error));
}
deleteRule(rule: ReplicationRule) {
if (rule) {
let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE",
"REPLICATION.DELETION_SUMMARY",
rule.name,
rule,
ConfirmationTargets.POLICY,
ConfirmationButtons.DELETE_CANCEL
);
this.deletionConfirmDialog.open(deletionMessage);
}
}
deleteOpe(rule: ReplicationRule) {
if (rule) {
let promiseLists: any[] = [];
Promise.all([this.jobList(rule.id)]).then(items => {
promiseLists.push(this.delOperate(rule));
Promise.all(promiseLists).then(item => {
this.selectedRow = null;
this.reload.emit(true);
let hnd = setInterval(() => this.ref.markForCheck(), 200);
setTimeout(() => clearInterval(hnd), 2000);
});
});
}
}
delOperate(rule: ReplicationRule) {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_REPLICATION';
operMessage.data.id = +rule.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = rule.name;
this.operationService.publishInfo(operMessage);
if (!this.canDeleteRule) {
let findedList = this.batchDelectionInfos.find(
data => data.name === rule.name
);
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPLICATION.DELETION_SUMMARY_FAILURE")
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
} else {
promiseLists.push(this.delOperate(+rule.id, rule.name));
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPLICATION.DELETION_SUMMARY_FAILURE')).subscribe(res => {
operateChanges(operMessage, OperationState.failure, res[1]);
});
return null;
}
Promise.all(promiseLists).then(item => {
this.selectedRow = null;
this.reload.emit(true);
let hnd = setInterval(() => this.ref.markForCheck(), 200);
setTimeout(() => clearInterval(hnd), 2000);
});
});
return toPromise<any>(this.replicationService
.deleteReplicationRule(+rule.id))
.then(() => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => operateChanges(operMessage, OperationState.success));
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => {
operateChanges(operMessage, OperationState.failure, res[1]);
});
} else {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
operateChanges(operMessage, OperationState.failure, res);
});
}
});
}
}
delOperate(ruleId: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<any>(this.replicationService.deleteReplicationRule(ruleId))
.then(() => {
this.translateService
.get("BATCH.DELETED_SUCCESS")
.subscribe(res => (findedList = BathInfoChanges(findedList, res)));
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get(
"REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED"
)
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
} else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
}
});
}
}

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>
<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>
<confirmation-dialog #replicationConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</div>
<confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</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 { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
import {ProjectDefaultService, ProjectService} from "../service/project.service";
import {OperationService} from "../operation/operation.service";
describe('Replication Component (inline template)', () => {
@ -231,7 +232,8 @@ describe('Replication Component (inline template)', () => {
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService }
{ provide: JobLogService, useClass: JobLogDefaultService },
{ provide: OperationService }
]
});
}));

View File

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

View File

@ -17,33 +17,36 @@
</div>
</div>
</div>
<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-dg-action-bar>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="provisionItemEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DEPLOY' | translate}}</button>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="itemAddInfoEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.ADDITIONAL_INFO' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]="r">
<clr-dg-cell><a href="javascript:void(0)" (click)="watchRepoClickEvt(r)"><span *ngIf="withAdmiral" class="list-img"><img [src]="getImgLink(r)"/></span>{{r.name}}</a></clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<div class="row">
<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-dg-action-bar>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="provisionItemEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DEPLOY' | translate}}</button>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="itemAddInfoEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.ADDITIONAL_INFO' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]="r">
<clr-dg-cell><a href="javascript:void(0)" (click)="watchRepoClickEvt(r)"><span *ngIf="withAdmiral" class="list-img"><img [src]="getImgLink(r)"/></span>{{r.name}}</a></clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="showDBStatusWarning" class="db-status-warning">
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
</span>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<hbr-gridview *ngIf="isCardView" #gridView style="position:relative;" [items]="repositories" [loading]="loading" [pageSize]="pageSize"
[currentPage]="currentPage" [totalCount]="totalCount" [expectScrollPercent]="90" [withAdmiral]="withAdmiral" (loadNextPageEvent)="loadNextPage()">
<ng-template let-item="item">
@ -90,5 +93,5 @@
</a>
</ng-template>
</hbr-gridview>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</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 { JobLogViewerComponent } from '../job-log-viewer/index';
import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {OperationService} from "../operation/operation.service";
describe('RepositoryComponentGridview (inline template)', () => {
@ -115,7 +116,8 @@ describe('RepositoryComponentGridview (inline template)', () => {
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: TagService, useClass: TagDefaultService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService }
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
{ provide: OperationService }
]
});
}));

View File

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

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">
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
<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 {LabelPieceComponent} from "../label-piece/label-piece.component";
import {LabelDefaultService, LabelService} from "../service/label.service";
import {OperationService} from "../operation/operation.service";
describe('TagComponent (inline template)', () => {
@ -112,7 +113,8 @@ describe('TagComponent (inline template)', () => {
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: TagService, useClass: TagDefaultService },
{ provide: ScanningResultService, useClass: ScanningResultDefaultService },
{provide: LabelService, useClass: LabelDefaultService}
{provide: LabelService, useClass: LabelDefaultService},
{ provide: OperationService }
]
});
}));

View File

@ -53,11 +53,10 @@ import {
clone,
} from "../utils";
import {CopyInputComponent} from "../push-image/copy-input.component";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
import {LabelService} from "../service/label.service";
import {operateChanges, OperateInfo, OperationState} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
export interface LabelState {
iconsShow: boolean;
@ -97,7 +96,6 @@ export class TagComponent implements OnInit, AfterViewInit {
staticBackdrop = true;
closable = false;
lastFilteredTagName: string;
batchDelectionInfos: BatchInfo[] = [];
inprogress: boolean;
openLabelFilterPanel: boolean;
openLabelFilterPiece: boolean;
@ -146,6 +144,7 @@ export class TagComponent implements OnInit, AfterViewInit {
private labelService: LabelService,
private translateService: TranslateService,
private ref: ChangeDetectorRef,
private operationService: OperationService,
private channel: ChannelService
) { }
@ -566,12 +565,8 @@ export class TagComponent implements OnInit, AfterViewInit {
deleteTags(tags: Tag[]) {
if (tags && tags.length) {
let tagNames: string[] = [];
this.batchDelectionInfos = [];
tags.forEach(tag => {
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;
@ -598,7 +593,7 @@ export class TagComponent implements OnInit, AfterViewInit {
if (tags && tags.length) {
let promiseLists: any[] = [];
tags.forEach(tag => {
promiseLists.push(this.delOperate(tag.signature, tag.name));
promiseLists.push(this.delOperate(tag));
});
Promise.all(promiseLists).then((item) => {
@ -609,34 +604,43 @@ export class TagComponent implements OnInit, AfterViewInit {
}
}
delOperate(signature: any, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
if (signature) {
delOperate(tag: Tag) {
// init operation info
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"),
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 " +
this.registryUrl + "/" + this.repoName + " " + name;
findedList = BathInfoChanges(findedList, res[0], false, true, wrongInfo);
let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl +
":4443 -d ~/.docker/trust remove -p " +
this.registryUrl + "/" + this.repoName +
" " + name;
operateChanges(operMessage, OperationState.failure, wrongInfo);
});
} else {
return toPromise<number>(this.tagService
.deleteTag(this.repoName, name))
.deleteTag(this.repoName, tag.name))
.then(
response => {
this.translateService.get("BATCH.DELETED_SUCCESS")
.subscribe(res => {
findedList = BathInfoChanges(findedList, res);
operateChanges(operMessage, OperationState.success);
});
}).catch(error => {
if (error.status === 503) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
operateChanges(operMessage, OperationState.failure, res[1]);
});
return;
}
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-ui": "^0.10.27",
"core-js": "^2.4.1",
"harbor-ui": "0.7.19-dev.8",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",
@ -50,6 +49,7 @@
"bootstrap": "4.0.0-alpha.5",
"codelyzer": "~2.0.0-beta.4",
"enhanced-resolve": "^3.0.0",
"harbor-ui": "0.7.19-test-3",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "~1.7.0",

View File

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

View File

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

View File

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

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>
</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 class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0">
@ -29,8 +17,7 @@
</ng-template>
<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-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<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 { ConfirmationAcknowledgement } from './confirmation-state-message';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared.const';
import {BatchInfo} from "./confirmation-batch-message";
@Component({
selector: 'confiramtion-dialog',
@ -32,32 +31,9 @@ export class ConfirmationDialogComponent implements OnDestroy {
dialogTitle: string = "";
dialogContent: string = "";
message: ConfirmationMessage;
resultLists: BatchInfo[] = [];
annouceSubscription: Subscription;
batchInfoSubscription: Subscription;
buttons: ConfirmationButtons;
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(
private confirmationService: ConfirmationDialogService,
private translate: TranslateService) {
@ -71,19 +47,12 @@ export class ConfirmationDialogComponent implements OnDestroy {
this.buttons = msg.buttons;
this.open();
});
this.batchInfoSubscription = confirmationService.confirmationBatch$.subscribe(data => {
this.resultLists = data;
});
}
ngOnDestroy(): void {
if (this.annouceSubscription) {
this.annouceSubscription.unsubscribe();
}
if (this.batchInfoSubscription) {
this.resultLists = [];
this.batchInfoSubscription.unsubscribe();
}
}
open(): void {
@ -91,7 +60,6 @@ export class ConfirmationDialogComponent implements OnDestroy {
}
close(): void {
this.resultLists = [];
this.opened = false;
}
@ -113,26 +81,6 @@ export class ConfirmationDialogComponent implements OnDestroy {
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 {
if (!this.message) {
// Inproper condition

View File

@ -16,17 +16,14 @@ import { Subject } from 'rxjs/Subject';
import { ConfirmationMessage } from './confirmation-message';
import { ConfirmationAcknowledgement } from './confirmation-state-message';
import {BatchInfo} from "./confirmation-batch-message";
@Injectable()
export class ConfirmationDialogService {
confirmationAnnoucedSource = new Subject<ConfirmationMessage>();
confirmationConfirmSource = new Subject<ConfirmationAcknowledgement>();
confirmationBatchSource = new Subject<BatchInfo[]>();
confirmationAnnouced$ = this.confirmationAnnoucedSource.asObservable();
confirmationConfirm$ = this.confirmationConfirmSource.asObservable();
confirmationBatch$ = this.confirmationBatchSource.asObservable();
// User confirm the action
public confirm(ack: ConfirmationAcknowledgement): void {
@ -42,7 +39,4 @@ export class ConfirmationDialogService {
public openComfirmDialog(message: ConfirmationMessage): void {
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 { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
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 { SessionService } from '../shared/session.service';
import { AppConfigService } from '../app-config.service';
import { NewUserModalComponent } from './new-user-modal.component';
import { UserService } from './user.service';
import { User } from './user';
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
/**
* NOTES:
* 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[]>;
selectedRow: User[] = [];
ISADMNISTRATOR: string = "USER.ENABLE_ADMIN_ACTION";
batchDelectionInfos: BatchInfo[] = [];
currentTerm: string;
totalCount: number = 0;
@ -72,6 +70,7 @@ export class UserComponent implements OnInit, OnDestroy {
private msgHandler: MessageHandlerService,
private session: SessionService,
private appConfigService: AppConfigService,
private operationService: OperationService,
private ref: ChangeDetectorRef) {
this.deletionSubscription = deletionDialogService.confirmationConfirm$.subscribe(confirmed => {
if (confirmed &&
@ -228,19 +227,15 @@ export class UserComponent implements OnInit, OnDestroy {
// Delete the specified user
deleteUsers(users: User[]): void {
let userArr: string[] = [];
this.batchDelectionInfos = [];
if (this.onlySelf) {
return;
}
if (users && users.length) {
users.forEach(user => {
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = user.username;
this.batchDelectionInfos.push(initBatchMessage);
userArr.push(user.username);
});
this.deletionDialogService.addBatchInfoList(this.batchDelectionInfos);
users.forEach(user => {
userArr.push(user.username);
});
}
// Confirm deletion
let msg: ConfirmationMessage = new ConfirmationMessage(
"USER.DELETION_TITLE",
@ -252,21 +247,12 @@ export class UserComponent implements OnInit, OnDestroy {
);
this.deletionDialogService.openComfirmDialog(msg);
}
}
delUser(users: User[]): void {
// this.batchInfoDialog.open();
let promiseLists: any[] = [];
if (users && users.length) {
users.forEach(user => {
let findedList = this.batchDelectionInfos.find(data => data.name === user.username);
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));
}
promiseLists.push(this.delOperate(user));
});
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);
return this.userService.deleteUser(id).then(() => {
delOperate(user: User) {
// 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 => {
findedList = BathInfoChanges(findedList, res);
operateChanges(operMessage, OperationState.success);
});
}).catch(error => {
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",
"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.",
"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.",

View File

@ -654,6 +654,27 @@
"SATURDAY": "Saturday",
"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.",
"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.",

View File

@ -612,6 +612,27 @@
"SATURDAY": "Saturday",
"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.",
"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.",

View File

@ -654,6 +654,27 @@
"SATURDAY": "周六",
"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": "发生未知错误,请稍后再试。",
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
"REPO_READ_ONLY": "Harbor被设置为只读模式在此模式下不能删除仓库、标签及推送镜像。",

View File

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