Fix replication ui related issues

Signed-off-by: FangyuanCheng <fangyuanc@vmware.com>
This commit is contained in:
FangyuanCheng 2019-04-04 16:04:46 +08:00
parent 3ed8d87406
commit e76c287a0c
13 changed files with 333 additions and 212 deletions

View File

@ -31,8 +31,6 @@ import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { Endpoint } from "../service/interface";
import { clone, compareValue, isEmptyObject } from "../utils";
const FAKE_PASSWORD = "rjGcfuRu";
@Component({
@ -72,14 +70,17 @@ export class CreateEditEndpointComponent
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private ref: ChangeDetectorRef
) { }
) {}
ngOnInit(): void {
this.endpointService.getAdapters().subscribe(adapters => {
this.adapterList = adapters || [];
}, error => {
this.errorHandler.error(error);
});
this.endpointService.getAdapters().subscribe(
adapters => {
this.adapterList = adapters || [];
},
error => {
this.errorHandler.error(error);
}
);
}
public get isValid(): boolean {
@ -118,7 +119,7 @@ export class CreateEditEndpointComponent
insecure: false,
name: "",
type: "harbor",
url: "",
url: ""
};
}
@ -167,8 +168,8 @@ export class CreateEditEndpointComponent
this.translateService
.get("DESTINATION.TITLE_EDIT")
.subscribe(res => (this.modalTitle = res));
this.endpointService.getEndpoint(targetId)
.subscribe(target => {
this.endpointService.getEndpoint(targetId).subscribe(
target => {
this.target = target;
// Keep data cache
this.initVal = clone(target);
@ -179,7 +180,9 @@ export class CreateEditEndpointComponent
this.open();
this.controlEnabled = true;
this.forceRefreshView(2000);
}, error => this.errorHandler.error(error));
},
error => this.errorHandler.error(error)
);
} else {
this.endpointId = "";
this.translateService
@ -213,18 +216,20 @@ export class CreateEditEndpointComponent
}
this.testOngoing = true;
this.endpointService.pingEndpoint(payload)
.subscribe(response => {
this.endpointService.pingEndpoint(payload).subscribe(
response => {
this.inlineAlert.showInlineSuccess({
message: "DESTINATION.TEST_CONNECTION_SUCCESS"
});
this.forceRefreshView(2000);
this.testOngoing = false;
}, error => {
},
error => {
this.inlineAlert.showInlineError("DESTINATION.TEST_CONNECTION_FAILURE");
this.forceRefreshView(2000);
this.testOngoing = false;
});
}
);
}
onSubmit() {
@ -240,8 +245,8 @@ export class CreateEditEndpointComponent
return; // Avoid duplicated submitting
}
this.onGoing = true;
this.endpointService.createEndpoint(this.target)
.subscribe(response => {
this.endpointService.createEndpoint(this.target).subscribe(
response => {
this.translateService
.get("DESTINATION.CREATED_SUCCESS")
.subscribe(res => this.errorHandler.info(res));
@ -249,14 +254,16 @@ export class CreateEditEndpointComponent
this.onGoing = false;
this.close();
this.forceRefreshView(2000);
}, error => {
},
error => {
this.onGoing = false;
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService.get(errorMessageKey).subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.forceRefreshView(2000);
});
}
);
}
updateEndpoint() {
@ -272,6 +279,7 @@ export class CreateEditEndpointComponent
if (isEmptyObject(changes)) {
return;
}
let changekeys: { [key: string]: any } = Object.keys(changes);
changekeys.forEach((key: string) => {
@ -283,8 +291,8 @@ export class CreateEditEndpointComponent
}
this.onGoing = true;
this.endpointService.updateEndpoint(this.target.id, payload)
.subscribe(response => {
this.endpointService.updateEndpoint(this.target.id, payload).subscribe(
response => {
this.translateService
.get("DESTINATION.UPDATED_SUCCESS")
.subscribe(res => this.errorHandler.info(res));
@ -292,14 +300,16 @@ export class CreateEditEndpointComponent
this.close();
this.onGoing = false;
this.forceRefreshView(2000);
}, error => {
},
error => {
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService.get(errorMessageKey).subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.onGoing = false;
this.forceRefreshView(2000);
});
}
);
}
handleErrorMessageKey(status: number): string {
@ -353,7 +363,6 @@ export class CreateEditEndpointComponent
if (!compareValue(this.formValues, data)) {
this.formValues = data;
this.inlineAlert.close();
}
}
}
@ -368,20 +377,36 @@ export class CreateEditEndpointComponent
}
for (let prop of Object.keys(this.target)) {
let field: any = this.initVal[prop];
if (!compareValue(field, this.target[prop])) {
changes[prop] = this.target[prop];
// Number
if (typeof field === "number") {
changes[prop] = +changes[prop];
}
if (typeof field !== "object") {
if (!compareValue(field, this.target[prop])) {
changes[prop] = this.target[prop];
// Number
if (typeof field === "number") {
changes[prop] = +changes[prop];
}
// Trim string value
if (typeof field === "string") {
changes[prop] = ("" + changes[prop]).trim();
// Trim string value
if (typeof field === "string") {
changes[prop] = ("" + changes[prop]).trim();
}
}
} else {
for (let pro of Object.keys(field)) {
if (!compareValue(field[pro], this.target[prop][pro])) {
changes[pro] = this.target[prop][pro];
// Number
if (typeof field[pro] === "number") {
changes[pro] = +changes[pro];
}
// Trim string value
if (typeof field[pro] === "string") {
changes[pro] = ("" + changes[pro]).trim();
}
}
}
}
}
return changes;
}
}

View File

@ -6,7 +6,8 @@
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasDeleteReplicationPermission" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasExecuteReplicationPermission" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon>&nbsp;{{'REPLICATION.REPLICATE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'" class="status-width">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column class="min-width">{{'REPLICATION.SRC_NAMESPACE' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.REPLICATION_MODE' | translate}}</clr-dg-column>
<clr-dg-column class="min-width">{{'REPLICATION.DESTINATION_NAMESPACE' | translate}}</clr-dg-column>
@ -15,6 +16,17 @@
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let p of changedRules; let i=index" [clrDgItem]="p" [style.backgroundColor]="(projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
<clr-dg-cell>{{p.name}}</clr-dg-cell>
<clr-dg-cell class="status-width">
<div [ngSwitch]="p.enabled">
<clr-tooltip *ngSwitchCase="false" class="tooltip-lg">
<clr-icon clrTooltipTrigger shape="exclamation-triangle" class="is-warning text-alignment" size="22"></clr-icon>Disabled
<clr-tooltip-content clrPosition="top-right" clrSize="xs" *clrIfOpen>
<span>{{'REPLICATION.RULE_DISABLED' | translate}}</span>
</clr-tooltip-content>
</clr-tooltip>
<div *ngSwitchCase="true" ><clr-icon shape="success-standard" class="is-success text-alignment" size="18"></clr-icon> Enabled</div>
</div>
</clr-dg-cell>
<clr-dg-cell class="min-width">
{{p.src_registry ? p.src_registry.name : ''}} : {{p.src_namespaces?.length>0 ? p.src_namespaces[0]: ''}}
<clr-tooltip>
@ -28,7 +40,7 @@
{{p.src_registry && p.src_registry.id > 0 ? 'pull-based' : 'push-based'}}
</clr-dg-cell>
<clr-dg-cell class="min-width">
{{p.dest_registry ? p.dest_registry.name : ''}} : {{p.dest_namespace? p.dest_namespace: ''}}
{{p.dest_registry ? p.dest_registry.name : ''}} : {{p.dest_namespace? p.dest_namespace: '-'}}
</clr-dg-cell>
<clr-dg-cell>{{p.trigger ? p.trigger.type : ''}}</clr-dg-cell>
<clr-dg-cell>

View File

@ -8,3 +8,6 @@
.min-width {
width: 224px;
}
.status-width {
width: 105px;
}

View File

@ -69,22 +69,21 @@
<h3 class="modal-title">Tasks</h3>
<div class="row flex-items-xs-between flex-items-xs-bottom">
<div class="action-select">
<div class="select filterTag" [hidden]="!isOpenFilterTag">
<div class="select filter-tag" [hidden]="!isOpenFilterTag">
<select (change)="selectFilter($event)">
<option value="resourceType">{{'REPLICATION.RESOURCE_TYPE' |translate}}</option>
<option value="resource">{{'REPLICATION.RESOURCE' | translate}}</option>
<option value="destination">{{'REPLICATION.DESTINATION' | translate}}</option>
<option value="resource_type">{{'REPLICATION.RESOURCE_TYPE' |translate}}</option>
<option value="status">{{'REPLICATION.STATUS' | translate}}</option>
</select>
</div>
<hbr-filter [withDivider]="true" (openFlag)="openFilter($event)"
filterPlaceholder='{{"REPLICATION.FILTER_PLACEHOLDER" | translate}}' (filterEvt)="doSearch($event)"></hbr-filter>
filterPlaceholder='{{"REPLICATION.FILTER_PLACEHOLDER" | translate}}'
(filterEvt)="doSearch($event)" [currentValue]="searchTask"></hbr-filter>
<span class="refresh-btn">
<clr-icon shape="refresh" (click)="refreshTasks()"></clr-icon>
</span>
</div>
</div>
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading">
<clr-datagrid (clrDgRefresh)="clrLoadTasks($event)" [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading">
<clr-dg-column>{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'resource_type'">{{'REPLICATION.RESOURCE_TYPE'
| translate}}</clr-dg-column>
@ -92,6 +91,8 @@
translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'dst_resource'">{{'REPLICATION.DESTINATION' |
translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' |
translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' |
translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.CREATION_TIME'
@ -104,6 +105,7 @@
<clr-dg-cell>{{t.resource_type}}</clr-dg-cell>
<clr-dg-cell>{{t.src_resource}}</clr-dg-cell>
<clr-dg-cell>{{t.dst_resource}}</clr-dg-cell>
<clr-dg-cell>{{t.operation}}</clr-dg-cell>
<clr-dg-cell>{{t.status}}</clr-dg-cell>
<clr-dg-cell>{{t.start_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{t.end_time | date: 'short'}}</clr-dg-cell>
@ -123,7 +125,7 @@
{{pagination.lastItem +1 }} {{'ROBOT_ACCOUNT.OF' |
translate}} </span>
{{pagination.totalItems }} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>

View File

@ -10,7 +10,7 @@
display: flex;
align-items: center;
>div:first-child {
width: 210px;
width: 250px;
}
>div:nth-child(2) {
width: 140px;
@ -72,7 +72,7 @@
display: flex;
justify-content: flex-end;
.filterTag {
.filter-tag {
float: left;
margin-top: 8px;
}
@ -85,5 +85,8 @@
color: #007CBB;
}
}
clr-datagrid {
margin-top: 20px;
}
}
}

View File

@ -4,8 +4,9 @@ import { ReplicationService } from "../../service/replication.service";
import { TranslateService } from '@ngx-translate/core';
import { finalize } from "rxjs/operators";
import { ErrorHandler } from "../../error-handler/error-handler";
import { ReplicationJob, ReplicationTasks, Comparator, ReplicationJobItem } from "../../service/interface";
import { CustomComparator } from "../../utils";
import { ReplicationJob, ReplicationTasks, Comparator, ReplicationJobItem, State } from "../../service/interface";
import { CustomComparator, DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting } from "../../utils";
import { RequestQueryParams } from "../../service/RequestQueryParams";
@Component({
selector: 'replication-tasks',
templateUrl: './replication-tasks.component.html',
@ -14,10 +15,16 @@ import { CustomComparator } from "../../utils";
export class ReplicationTasksComponent implements OnInit {
isOpenFilterTag: boolean;
selectedRow: [];
loading = false;
currentPage: number = 1;
currentPagePvt: number = 0;
totalCount: number = 0;
pageSize: number = DEFAULT_PAGE_SIZE;
currentState: State;
loading = true;
searchTask: string;
defaultFilter = "resourceType";
tasks: ReplicationTasks[] = [];
defaultFilter = "resource_type";
tasks: ReplicationTasks;
taskItem: ReplicationTasks[] = [];
tasksCopy: ReplicationTasks[] = [];
stopOnGoing: boolean;
executions: ReplicationJobItem[];
@ -37,7 +44,6 @@ export class ReplicationTasksComponent implements OnInit {
) { }
ngOnInit(): void {
this.clrLoadTasks();
this.searchTask = '';
this.getExecutionDetail();
}
@ -97,34 +103,49 @@ export class ReplicationTasksComponent implements OnInit {
return this.replicationService.getJobBaseUrl() + "/executions/" + this.executionId + "/tasks/" + taskId + "/log";
}
clrLoadTasks(): void {
this.loading = true;
this.replicationService.getReplicationTasks(this.executionId)
.pipe(finalize(() => (this.loading = false)))
.subscribe(tasks => {
if (this.defaultFilter === 'resourceType') {
this.tasks = tasks.filter(x =>
x.resource_type.includes(this.searchTask)
);
} else if (this.defaultFilter === 'resource') {
this.tasks = tasks.filter(x =>
x.src_resource.includes(this.searchTask)
);
} else if (this.defaultFilter === 'destination') {
this.tasks = tasks.filter(x =>
x.dst_resource.includes(this.searchTask)
);
} else {
this.tasks = tasks.filter(x =>
x.status.includes(this.searchTask)
);
clrLoadTasks(state: State): void {
if (!state || !state.page) {
return;
}
// Keep it for future filter
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber !== this.currentPagePvt) {
// load data
let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + pageNumber);
params.set("page_size", '' + this.pageSize);
if (this.searchTask && this.searchTask !== "") {
params.set(this.defaultFilter, this.searchTask);
}
this.tasksCopy = tasks.map(x => Object.assign({}, x));
this.loading = true;
this.replicationService.getReplicationTasks(this.executionId, params)
.pipe(finalize(() => (this.loading = false)))
.subscribe(res => {
this.totalCount = res.length;
this.tasks = res; // Keep the data
this.taskItem = this.tasks.filter(tasks => tasks.resource_type !== "");
this.taskItem = doFiltering<ReplicationTasks>(this.taskItem, state);
this.taskItem = doSorting<ReplicationTasks>(this.taskItem, state);
this.currentPagePvt = pageNumber;
},
error => {
this.errorHandler.error(error);
});
} else {
this.taskItem = this.tasks.filter(tasks => tasks.resource_type !== "");
// Do customized filter
this.taskItem = doFiltering<ReplicationTasks>(this.taskItem, state);
// Do customized sorting
this.taskItem = doSorting<ReplicationTasks>(this.taskItem, state);
}
}
onBack(): void {
this.router.navigate(["harbor", "replications"]);
@ -138,15 +159,41 @@ export class ReplicationTasksComponent implements OnInit {
// refresh icon
refreshTasks(): void {
this.searchTask = '';
this.clrLoadTasks();
this.loading = true;
this.replicationService.getReplicationTasks(this.executionId)
.subscribe(res => {
this.tasks = res;
this.loading = false;
},
error => {
this.loading = false;
this.errorHandler.error(error);
});
}
doSearch(value: string): void {
public doSearch(value: string): void {
if (!value) {
return;
}
this.searchTask = value.trim();
this.clrLoadTasks();
this.loading = true;
this.currentPage = 1;
if (this.currentPagePvt === 1) {
// Force reloading
let st: State = this.currentState;
if (!st) {
st = {
page: {}
};
}
st.page.from = 0;
st.page.to = this.pageSize - 1;
st.page.size = this.pageSize;
this.currentPagePvt = 0;
this.clrLoadTasks(st);
}
}
openFilter(isOpen: boolean): void {

View File

@ -24,19 +24,21 @@
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="row flex-items-xs-between jobsRow">
<h5 class="flex-items-xs-bottom option-left-down">{{'REPLICATION.REPLICATION_EXECUTIONS' | translate}}</h5>
<div class="flex-items-xs-bottom option-right-down">
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{"REPLICATION.ADVANCED" | translate}}</button>
</div>
</div>
<div class="row flex-items-xs-right row-right" [hidden]="currentJobSearchOption === 0">
<div class="select select-status">
<select (change)="doFilterJobStatus($event)">
<option *ngFor="let j of jobStatus" value="{{j.key}}" [selected]="currentJobStatus.key === j.key">{{j.description | translate}}</option>
</select>
</div>
<div class="flex-items-xs-middle">
<hbr-datetime [dateInput]="search.startTime" (search)="doJobSearchByStartTime($event)"></hbr-datetime>
<hbr-datetime [dateInput]="search.endTime" [oneDayOffset]="true" (search)="doJobSearchByEndTime($event)"></hbr-datetime>
<div class="row flex-items-xs-between flex-items-xs-bottom">
<div class="execution-select">
<div class="select filter-tag" [hidden]="!isOpenFilterTag">
<select (change)="doFilterJob($event)">
<option value="trigger">{{'REPLICATION.REPLICATION_TRIGGER' |translate}}</option>
<option value="status">{{'REPLICATION.STATUS' | translate}}</option>
</select>
</div>
<hbr-filter [withDivider]="true" (openFlag)="openFilter($event)"
filterPlaceholder='{{"REPLICATION.FILTER_EXECUTIONS_PLACEHOLDER" | translate}}'
(filterEvt)="doSearchExecutions($event)" [currentValue]="currentTerm"></hbr-filter>
<span class="refresh-btn">
<clr-icon shape="refresh" (click)="refreshJobs()"></clr-icon>
</span>
</div>
</div>
</div>
</div>
@ -61,11 +63,19 @@
</clr-dg-cell>
<clr-dg-cell>{{j.trigger}}</clr-dg-cell>
<clr-dg-cell>{{j.start_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{'3mins'}}</clr-dg-cell>
<clr-dg-cell>{{getDuration(j)}}</clr-dg-cell>
<clr-dg-cell>
{{'90%'}}
{{(j.succeed > 0 ? j.succeed / j.total : 0) | percent }}
</clr-dg-cell>
<clr-dg-cell>
{{j.status}}
<clr-tooltip>
<clr-icon *ngIf="j.status_text" clrTooltipTrigger shape="info-circle" size="20"></clr-icon>
<clr-tooltip-content [clrPosition]="'left'" clrSize="md" *clrIfOpen>
<span>{{j.status_text}}</span>
</clr-tooltip-content>
</clr-tooltip>
</clr-dg-cell>
<clr-dg-cell>{{j.status}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="showPaginationIndex">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>

View File

@ -13,6 +13,25 @@
padding-right: 16px;
}
.execution-select {
padding-right: 18px;
height: 24px;
display: flex;
justify-content: flex-end;
padding-top: 25px;
.filter-tag {
float: left;
margin-top: 8px;
}
.refresh-btn {
cursor: pointer;
margin-top: 7px;
&:hover {
color: #007CBB;
}
}
}
.rightPos{
position: absolute;
right: 35px;
@ -48,4 +67,10 @@
margin-top: 24px;
}
}
}
clr-datagrid {
::ng-deep .datagrid-table {
position: static;
}
}

View File

@ -260,7 +260,6 @@ describe('Replication Component (inline template)', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
comp.doFilterJobStatus('finished');
let el: HTMLElement = deJobs.nativeElement;
fixture.detectChanges();
expect(el).toBeTruthy();
@ -272,8 +271,6 @@ describe('Replication Component (inline template)', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
comp.doJobSearchByStartTime('2017-05-01');
comp.doJobSearchByEndTime('2015-05-25');
let el: HTMLElement = deJobs.nativeElement;
fixture.detectChanges();
expect(el).toBeTruthy();

View File

@ -53,9 +53,16 @@ import {
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-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 {
operateChanges,
OperationState,
OperateInfo
} from "../operation/operate";
import { OperationService } from "../operation/operation.service";
import { Router } from "@angular/router";
const ONE_HOUR_SECONDS: number = 3600;
const ONE_MINUTE_SECONDS: number = 60;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
const ruleStatus: { [key: string]: any } = [
{ key: "all", description: "REPLICATION.ALL_STATUS" },
@ -63,26 +70,11 @@ const ruleStatus: { [key: string]: any } = [
{ key: "0", description: "REPLICATION.DISABLED" }
];
const jobStatus: { [key: string]: any } = [
{ key: "all", description: "REPLICATION.ALL" },
{ key: "pending", description: "REPLICATION.PENDING" },
{ key: "running", description: "REPLICATION.RUNNING" },
{ key: "error", description: "REPLICATION.ERROR" },
{ key: "retrying", description: "REPLICATION.RETRYING" },
{ key: "stopped", description: "REPLICATION.STOPPED" },
{ key: "finished", description: "REPLICATION.FINISHED" },
{ key: "canceled", description: "REPLICATION.CANCELED" }
];
export class SearchOption {
ruleId: number | string;
ruleName: string = "";
repoName: string = "";
trigger: string = "";
status: string = "";
startTime: string = "";
startTimestamp: string = "";
endTime: string = "";
endTimestamp: string = "";
page: number = 1;
pageSize: number = DEFAULT_PAGE_SIZE;
}
@ -109,12 +101,12 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@Output() goToRegistry = new EventEmitter<any>();
search: SearchOption = new SearchOption();
isOpenFilterTag: boolean;
ruleStatus = ruleStatus;
currentRuleStatus: { key: string; description: string };
jobStatus = jobStatus;
currentJobStatus: { key: string; description: string };
currentTerm: string;
defaultFilter = "trigger";
changedRules: ReplicationRule[];
@ -125,7 +117,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
hiddenJobList = true;
jobs: ReplicationJobItem[];
currentJobSearchOption: number;
@ViewChild(ListReplicationRuleComponent)
listReplicationRule: ListReplicationRuleComponent;
@ -133,7 +124,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@ViewChild(CreateEditRuleComponent)
createEditPolicyComponent: CreateEditRuleComponent;
@ViewChild("replicationConfirmDialog")
replicationConfirmDialog: ConfirmationDialogComponent;
@ -142,10 +132,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("start_time", "date");
>("start_time", "date");
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("end_time", "date");
>("end_time", "date");
// Server driven pagination
currentPage: number = 1;
@ -160,7 +150,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
private errorHandler: ErrorHandler,
private replicationService: ReplicationService,
private operationService: OperationService,
private translateService: TranslateService) { }
private translateService: TranslateService
) {}
public get showPaginationIndex(): boolean {
return this.totalCount > 0;
@ -168,8 +159,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
ngOnInit() {
this.currentRuleStatus = this.ruleStatus[0];
this.currentJobStatus = this.jobStatus[0];
this.currentJobSearchOption = 0;
}
ngOnDestroy() {
@ -215,43 +204,23 @@ export class ReplicationComponent implements OnInit, OnDestroy {
// Pagination
params.set("page", "" + pageNumber);
params.set("page_size", "" + this.pageSize);
// Search by status
if (this.search.status.trim()) {
params.set("status", this.search.status);
}
// Search by repository
if (this.search.repoName.trim()) {
params.set("repository", this.search.repoName);
}
// Search by timestamps
if (this.search.startTimestamp.trim()) {
params.set("start_time", this.search.startTimestamp);
}
if (this.search.endTimestamp.trim()) {
params.set("end_time", this.search.endTimestamp);
if (this.currentTerm && this.currentTerm !== "") {
params.set(this.defaultFilter, this.currentTerm);
}
this.jobsLoading = true;
// Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false;
this.replicationService.getExecutions(this.search.ruleId, params)
.subscribe(response => {
this.replicationService.getExecutions(this.search.ruleId, params).subscribe(
response => {
this.totalCount = response.metadata.xTotalCount;
this.jobs = response.data;
if (!this.timerDelay) {
this.timerDelay = timer(10000, 10000).subscribe(() => {
let count: number = 0;
this.jobs.forEach(job => {
if (
job.status === "pending" ||
job.status === "running" ||
job.status === "retrying"
job.status === "InProgress"
) {
count++;
}
@ -264,18 +233,30 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
});
}
// Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false;
}, error => {
},
error => {
this.jobsLoading = false;
this.errorHandler.error(error);
});
}
);
}
public doSearchExecutions(terms: string): void {
if (!terms) {
return;
}
this.currentTerm = terms.trim();
// Trigger data loading and start from first page
this.jobsLoading = true;
this.currentPage = 1;
this.jobsLoading = true;
// Force reloading
this.loadFirstPage();
}
loadFirstPage(): void {
let st: State = this.currentState;
if (!st) {
@ -294,10 +275,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
if (rule && rule.id) {
this.hiddenJobList = false;
this.search.ruleId = rule.id || "";
this.search.repoName = "";
this.search.status = "";
this.currentJobSearchOption = 0;
this.currentJobStatus = { key: "all", description: "REPLICATION.ALL" };
this.loadFirstPage();
}
}
@ -325,7 +302,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
let rule: ReplicationRule = message.data;
if (rule) {
forkJoin(this.replicationOperate(rule)).subscribe((item) => {
forkJoin(this.replicationOperate(rule)).subscribe(item => {
this.selectOneRule(rule);
});
}
@ -335,30 +312,39 @@ export class ReplicationComponent implements OnInit, OnDestroy {
replicationOperate(rule: ReplicationRule): Observable<any> {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.REPLICATION';
operMessage.name = "OPERATION.REPLICATION";
operMessage.data.id = rule.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = rule.name;
this.operationService.publishInfo(operMessage);
return this.replicationService.replicateRule(+rule.id)
.pipe(map(response => {
this.translateService.get('BATCH.REPLICATE_SUCCESS')
.subscribe(res => operateChanges(operMessage, OperationState.success));
})
, catchError(error => {
if (error && error.status === 412) {
return forkJoin(this.translateService.get('BATCH.REPLICATE_FAILURE'),
this.translateService.get('REPLICATION.REPLICATE_SUMMARY_FAILURE'))
.pipe(map(function (res) {
operateChanges(operMessage, OperationState.failure, res[1]);
}));
} else {
return this.translateService.get('BATCH.REPLICATE_FAILURE').pipe(map(res => {
return this.replicationService.replicateRule(+rule.id).pipe(
map(response => {
this.translateService
.get("BATCH.REPLICATE_SUCCESS")
.subscribe(res =>
operateChanges(operMessage, OperationState.success)
);
}),
catchError(error => {
if (error && error.status === 412) {
return forkJoin(
this.translateService.get("BATCH.REPLICATE_FAILURE"),
this.translateService.get("REPLICATION.REPLICATE_SUMMARY_FAILURE")
).pipe(
map(function(res) {
operateChanges(operMessage, OperationState.failure, res[1]);
})
);
} else {
return this.translateService.get("BATCH.REPLICATE_FAILURE").pipe(
map(res => {
operateChanges(operMessage, OperationState.failure, res);
}));
}
}));
})
);
}
})
);
}
customRedirect(rule: ReplicationRule) {
@ -370,21 +356,17 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.listReplicationRule.retrieveRules(ruleName);
}
doFilterJobStatus($event: any) {
if ($event && $event.target && $event.target["value"]) {
let status = $event.target["value"];
this.currentJobStatus = this.jobStatus.find((r: any) => r.key === status);
if (this.currentJobStatus.key === "all") {
status = "";
}
this.search.status = status;
this.doSearchJobs(this.search.repoName);
}
doFilterJob($event: any): void {
this.defaultFilter = $event["target"].value;
this.doSearchJobs(this.currentTerm);
}
doSearchJobs(repoName: string) {
this.search.repoName = repoName;
doSearchJobs(terms: string) {
if (!terms) {
return;
}
this.currentTerm = terms.trim();
this.currentPage = 1;
this.loadFirstPage();
}
@ -434,7 +416,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.selectedRow = [];
})
)
.subscribe(() => { });
.subscribe(() => {});
}
}
@ -468,14 +450,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
refreshJobs() {
this.currentJobStatus = this.jobStatus[0];
this.search.startTime = " ";
this.search.endTime = " ";
this.search.repoName = "";
this.search.startTimestamp = "";
this.search.endTimestamp = "";
this.search.status = "";
this.currentTerm = "";
this.currentPage = 1;
let st: State = {
@ -488,19 +463,36 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.clrLoadJobs(st);
}
toggleSearchJobOptionalName(option: number) {
option === 1
? (this.currentJobSearchOption = 0)
: (this.currentJobSearchOption = 1);
openFilter(isOpen: boolean): void {
if (isOpen) {
this.isOpenFilterTag = true;
} else {
this.isOpenFilterTag = false;
}
}
getDuration(j: ReplicationJobItem) {
if (!j) {
return;
}
if (j.status === "Failed") {
return "-";
}
let start_time = new Date(j.start_time).getTime();
let end_time = new Date(j.end_time).getTime();
let timesDiff = end_time - start_time;
let timesDiffSeconds = timesDiff / 1000;
let minutes = Math.floor(((timesDiffSeconds % ONE_DAY_SECONDS) % ONE_HOUR_SECONDS) / ONE_MINUTE_SECONDS);
let seconds = Math.floor(timesDiffSeconds % ONE_MINUTE_SECONDS);
if (minutes > 0) {
return minutes + "m" + seconds + "s";
}
doJobSearchByStartTime(fromTimestamp: string) {
this.search.startTimestamp = fromTimestamp;
this.loadFirstPage();
}
if (seconds > 0) {
return seconds + "s";
}
doJobSearchByEndTime(toTimestamp: string) {
this.search.endTimestamp = toTimestamp;
this.loadFirstPage();
if (seconds <= 0 && timesDiff > 0) {
return timesDiff + 'ms';
}
}
}

View File

@ -173,6 +173,7 @@ export interface ReplicationJobItem extends Base {
*/
export interface ReplicationTasks extends Base {
[key: string]: any | any[];
operation: string;
id: number;
execution_id: number;
resource_type: string;

View File

@ -67,7 +67,8 @@ export abstract class ReplicationService {
* @memberOf ReplicationService
*/
abstract getReplicationTasks(
executionId: number | string
executionId: number | string,
queryParams?: RequestQueryParams
): Observable<ReplicationTasks>;
/**
* Create new replication rule.
@ -287,14 +288,16 @@ export class ReplicationDefaultService extends ReplicationService {
}
public getReplicationTasks(
executionId: number | string
executionId: number | string,
queryParams?: RequestQueryParams
): Observable<ReplicationTasks> {
if (!executionId) {
return observableThrowError("Bad argument");
}
let url: string = `${this._replicateUrl}/executions/${executionId}/tasks`;
return this.http
.get(url, HTTP_GET_OPTIONS)
.get(url,
queryParams ? buildHttpRequestOptions(queryParams) : HTTP_GET_OPTIONS)
.pipe(map(response => response.json() as ReplicationTasks)
, catchError(error => observableThrowError(error)));
}

View File

@ -331,6 +331,7 @@
"OF": "of"
},
"REPLICATION": {
"OPERATION": "Operation",
"CURRENT": "current",
"FILTER_PLACEHOLDER": "Filter Tasks",
"STOP_TITLE": "Confirm Stop Executions",