mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-28 03:27:41 +01:00
Merge pull request #7302 from pureshine/registry-update
Fix replication ui related issues
This commit is contained in:
commit
2644c52926
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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> {{'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> {{'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>
|
||||
|
@ -8,3 +8,6 @@
|
||||
.min-width {
|
||||
width: 224px;
|
||||
}
|
||||
.status-width {
|
||||
width: 105px;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -262,7 +262,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();
|
||||
@ -274,8 +273,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();
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +175,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;
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -340,6 +340,7 @@
|
||||
"OF": "of"
|
||||
},
|
||||
"REPLICATION": {
|
||||
"OPERATION": "Operation",
|
||||
"CURRENT": "current",
|
||||
"FILTER_PLACEHOLDER": "Filter Tasks",
|
||||
"STOP_TITLE": "Confirm Stop Executions",
|
||||
|
Loading…
Reference in New Issue
Block a user