replication-ng

Signed-off-by: FangyuanCheng <fangyuanc@vmware.com>
This commit is contained in:
FangyuanCheng 2019-03-18 18:22:59 +08:00
parent 27b65f4739
commit 839f68f5fc
34 changed files with 674 additions and 137 deletions

View File

@ -258,9 +258,9 @@ export const DefaultServiceConfig: IServiceConfig = {
systemInfoEndpoint: "/api/systeminfo",
repositoryBaseEndpoint: "/api/repositories",
logBaseEndpoint: "/api/logs",
targetBaseEndpoint: "/api/targets",
targetBaseEndpoint: "/api/registries",
replicationRuleEndpoint: "/api/policies/replication",
replicationJobEndpoint: "/api/jobs/replication",
replicationBaseEndpoint: "/api/replication/executions",
vulnerabilityScanningBaseEndpoint: "/api/repositories",
configurationEndpoint: "/api/configurations",
enablei18Support: false,
@ -293,11 +293,11 @@ It supports partially overriding. For the items not overridden, default values w
* **logBaseEndpoint:** The base endpoint of the service used to handle the recent access logs. Default is "/api/logs".
* **targetBaseEndpoint:** The base endpoint of the service used to handle the registry endpoints. Default is "/api/targets".
* **targetBaseEndpoint:** The base endpoint of the service used to handle the registry endpoints. Default is "/api/registries".
* **replicationRuleEndpoint:** The base endpoint of the service used to handle the replication rules. Default is "/api/policies/replication".
* **replicationJobEndpoint:** The base endpoint of the service used to handle the replication jobs. Default is "/api/jobs/replication".
* **replicationBaseEndpoint:** The base endpoint of the service used to handle the replication executions. Default is "/api/replication/executions".
* **vulnerabilityScanningBaseEndpoint:** The base endpoint of the service used to handle the vulnerability scanning results.Default value is "/api/repositories".

View File

@ -26,5 +26,9 @@
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="5">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.STOP' | translate}}</button>
</ng-template>
</div>
</clr-modal>

View File

@ -40,6 +40,7 @@ import { OperationService } from "../operation/operation.service";
import {FilterLabelComponent} from "./filter-label.component";
import {LabelService} from "../service/label.service";
import {LabelPieceComponent} from "../label-piece/label-piece.component";
import { RouterTestingModule } from '@angular/router/testing';
describe("CreateEditRuleComponent (inline template)", () => {
let mockRules: ReplicationRule[] = [
@ -226,13 +227,13 @@ describe("CreateEditRuleComponent (inline template)", () => {
let spyEndpoint: jasmine.Spy;
let config: IServiceConfig = {
replicationJobEndpoint: "/api/jobs/replication/testing",
replicationBaseEndpoint: "/api/replication/executions/testing",
targetBaseEndpoint: "/api/targets/testing"
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [SharedModule, NoopAnimationsModule],
imports: [SharedModule, NoopAnimationsModule, RouterTestingModule],
declarations: [
ReplicationComponent,
ListReplicationRuleComponent,
@ -277,7 +278,7 @@ describe("CreateEditRuleComponent (inline template)", () => {
replicationService,
"getReplicationRule"
).and.returnValue(Promise.resolve(mockRule));
spyJobs = spyOn(replicationService, "getJobs").and.returnValues(
spyJobs = spyOn(replicationService, "getExecutions").and.returnValues(
Promise.resolve(mockJob)
);

View File

@ -83,10 +83,9 @@ export const DefaultServiceConfig: IServiceConfig = {
systemInfoEndpoint: "/api/systeminfo",
repositoryBaseEndpoint: "/api/repositories",
logBaseEndpoint: "/api/logs",
targetBaseEndpoint: "/api/targets",
replicationBaseEndpoint: "/api/replications",
replicationRuleEndpoint: "/api/policies/replication",
replicationJobEndpoint: "/api/jobs/replication",
targetBaseEndpoint: "/api/registries",
replicationBaseEndpoint: "/api/replication/executions",
replicationRuleEndpoint: "/api/replication/policies",
vulnerabilityScanningBaseEndpoint: "/api/repositories",
projectPolicyEndpoint: "/api/projects/configs",
projectBaseEndpoint: "/api/projects",

View File

@ -7,28 +7,32 @@
<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 [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column *ngIf="!projectScope">{{'REPLICATION.PROJECT' | 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>
<clr-dg-column>{{'REPLICATION.LAST_REPLICATION' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'trigger'">{{'REPLICATION.REPLICATION_TRIGGER' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-column >{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'trigger'">{{'REPLICATION.TRIGGER_MODE' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let p of changedRules" [clrDgItem]="p" [style.backgroundColor]="(projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
<clr-dg-cell>{{p.name}}</clr-dg-cell>
<clr-dg-cell class="min-width">
{{p.src_registry_id>0? srcRegistry : 'current'}} : {{p.src_namespaces?.length>0 ? p.src_namespaces[0]: ''}}
<clr-tooltip>
<clr-icon *ngIf="p.src_namespaces && p.src_namespaces.length > 1" clrTooltipTrigger shape="ellipsis-horizontal" size="18"></clr-icon>
<clr-tooltip-content clrPosition="top-right" clrSize="md" *clrIfOpen>
<span>{{p.src_namespaces}}</span>
</clr-tooltip-content>
</clr-tooltip>
</clr-dg-cell>
<clr-dg-cell>
<div [ngSwitch]="hasDeletedLabel(p)">
<clr-tooltip *ngSwitchCase="'disabled'" 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="'enabled'" ><clr-icon shape="success-standard" class="is-success text-alignment" size="18"></clr-icon> Enabled</div>
</div>
{{p.src_registry_id>0? 'pull-based' : 'push-based'}}
</clr-dg-cell>
<clr-dg-cell *ngIf="!projectScope">
<a href="javascript:void(0)" (click)="$event.stopPropagation(); redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
<clr-dg-cell class="min-width">
{{p.dest_registry_id>0? srcRegistry : 'current'}} : {{p.dest_namespace? p.dest_namespace: ''}}
</clr-dg-cell>
<clr-dg-cell>{{p.update_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{p.trigger ? p.trigger.type : ''}}</clr-dg-cell>
<clr-dg-cell>
{{p.description ? trancatedDescription(p.description) : '-'}}
<clr-tooltip>
@ -38,8 +42,6 @@
</clr-tooltip-content>
</clr-tooltip>
</clr-dg-cell>
<clr-dg-cell>{{p.targets?.length>0 ? p.targets[0].name : ''}}</clr-dg-cell>
<clr-dg-cell>{{p.trigger ? p.trigger.kind : ''}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} </span>{{pagination.totalItems }} {{'REPLICATION.ITEMS' | translate}}

View File

@ -4,4 +4,7 @@
.text-alignment {
vertical-align: text-bottom;
}
}
.min-width {
width: 224px;
}

View File

@ -82,6 +82,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
changedRules: ReplicationRule[];
ruleName: string;
canDeleteRule: boolean;
srcRegistry: string = 'docker hub';
selectedRow: ReplicationRule;
@ -201,7 +202,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;
let count = 0;
return toPromise<ReplicationJob>(this.replicationService.getJobs(id))
return toPromise<ReplicationJob>(this.replicationService.getExecutions(id))
.then(response => {
ruleData = response.data;
if (ruleData.length) {

View File

@ -1,8 +1,11 @@
import { Type } from '@angular/core';
import { ReplicationComponent } from './replication.component';
import { ReplicationTasksComponent } from './replication-tasks/replication-tasks.component';
export * from './replication.component';
export * from './replication-tasks/replication-tasks.component';
export const REPLICATION_DIRECTIVES: Type<any>[] = [
ReplicationComponent
ReplicationComponent,
ReplicationTasksComponent
];

View File

@ -0,0 +1,117 @@
<div class="replication-tasks">
<section class="overview-section">
<div class="title-wrapper clr-row">
<div class="clr-col-lg-9">
<div>
<a (click)="onBack()" class="onback">< {{'PROJECT_DETAIL.REPLICATION'| translate}}</a>
</div>
<div class="title-block clr-row">
<div class="clr-col-2">
<h2 class="custom-h2">{{'REPLICATION.REPLICATION_EXECUTION'| translate}}</h2>
<h4>{{executionId}}</h4>
</div>
<div class="clr-col-3">
<span class="label label-info" *ngIf="executions === 'InProgress'">In Progress</span>
<span class="label label-success" *ngIf="executions === 'success'">Success</span>
<span class="label label-danger" *ngIf="executions=== 'failture'">Failture</span>
</div>
</div>
</div>
<div class="clr-col-3">
<button class="btn btn-outline" (click)="stopJob()" [disabled]="stopOnGoing">{{'REPLICATION.STOPJOB' | translate}}</button>
</div>
</div>
<div class="execution-block row">
<div class="clr-col-sm-12 clr-col-md-6">
<div class="flex-block">
<section class="execution-detail-label">
<section class="detail-row">
<span class="label label-purple detail-span">{{'REPLICATION.SUCCESS'| translate}}</span>
<div class="execution-details">{{'1'}}</div>
</section>
<section class="detail-row">
<span class="label label-red detail-span">{{'REPLICATION.FAILTURE'| translate}}</span>
<div class="execution-details">{{'2'}}</div>
</section>
<section class="detail-row">
<span class="label label-light-blue detail-span">{{'REPLICATION.IN_PROGRESS'| translate}}</span>
<div class="execution-details">{{'3'}}</div>
</section>
</section>
</div>
</div>
<div class="clr-col-sm-12 clr-col-md-6">
<div class="executions-detail">
<div class="policy">
<label>{{'REPLICATION.POLICY' | translate}} :</label>
<span>{{'My 1st policy'}}</span>
</div>
<div class="trigger">
<label for="">{{'REPLICATION.TRIGGER_MODE' | translate}} :</label>
<span>{{'Schedule'}}</span>
</div>
<div class="start-time">
<label for="">{{'REPLICATION.CREATION_TIME' | translate}} :</label>
<span>{{'3/14/19, 2:26 PM'}}</span>
</div>
</div>
</div>
</div>
</section>
<div class="tasks-detail">
<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">
<select>
<option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option>
<option value="repository">{{"CONFIG.REPOSITORY" | translate | lowercase}}</option>
<option value="tag">{{"REPOSITORY.TAG" | translate | lowercase}}</option>
<option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option>
</select>
</div>
<hbr-filter [withDivider]="true" (openFlag)="openFilter($event)" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}'></hbr-filter>
<span class="refresh-btn">
<clr-icon shape="refresh"></clr-icon>
<span class="spinner spinner-inline" hidden></span>
</span>
</div>
</div>
<clr-datagrid [(clrDgSelected)]="selectedRow">
<clr-dg-column>{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'resource_type'">{{'REPLICATION.RECOURCE_TYPE' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'src_resource'">{{'REPLICATION.RECOURCE' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'dst_resource'">{{'REPLICATION.DESTINATION' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="endTimeComparator">{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let t of tasks">
<clr-dg-cell>{{t.id}}</clr-dg-cell>
<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.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>
<clr-dg-cell>
<span *ngIf="t.status=='InProgress'; else elseBlock" class="label">{{'REPLICATION.NO_LOGS' | translate}}</span>
<ng-template #elseBlock>
<a target="_blank" [href]="viewLog(t.id)">
<clr-icon shape="list"></clr-icon>
</a>
</ng-template>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}}
-
{{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-footer>
</clr-datagrid>
</div>
</div>

View File

@ -0,0 +1,63 @@
.replication-tasks {
.overview-section {
.title-wrapper {
.onback{
color: #007cbb;
font-size: 12px;
cursor: pointer;
}
h4 {
margin-top: 8px;
}
}
.execution-block {
margin-top: 24px;
display: flex;
flex-wrap: row wrap;
.execution-detail-label {
margin-right: 10px;
text-align: left;
.detail-row {
display: flex;
.detail-span {
flex:0 0 100px;
font-weight: 600;
margin-top: 10px;
}
.execution-details {
width: 200px;
margin: 8px 35px;
}
}
}
.executions-detail {
font-weight: 600;
span {
margin-left: 5px;
}
}
}
}
.tasks-detail {
margin-top: 65px;
.action-select {
padding-right: 18px;
height: 24px;
display: flex;
justify-content: flex-end;
.filterTag {
float: left;
margin-top: 8px;
}
.refresh-btn {
cursor: pointer;
margin-top: 7px;
}
.refresh-btn:hover {
color: #007CBB;
}
}
}
}

View File

@ -0,0 +1,91 @@
import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';
import { ReplicationService } from "../../service/replication.service";
import { map, catchError } from "rxjs/operators";
import { Observable, forkJoin, throwError as observableThrowError } from "rxjs";
import { ErrorHandler } from "../../error-handler/error-handler";
import { ReplicationJob, ReplicationTasks, Comparator, ReplicationJobItem } from "../../service/interface";
import { CustomComparator } from "../../utils";
@Component({
selector: 'replication-tasks',
templateUrl: './replication-tasks.component.html',
styleUrls: ['./replication-tasks.component.scss']
})
export class ReplicationTasksComponent implements OnInit {
isOpenFilterTag: boolean;
selectedRow: [];
tasks: ReplicationTasks[] = [];
stopOnGoing: boolean;
executions: string = 'InProgress';
@Input() executionId: string;
startTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("start_time", "date");
endTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("end_time", "date");
constructor(
private router: Router,
private replicationService: ReplicationService,
private errorHandler: ErrorHandler,
) { }
ngOnInit(): void {
// this.getExecutions();
this.getTasks();
// this.executions.status = 'success';
}
// getExecutions(): void {
// if (this.executionId) {
// toPromise<ReplicationJob>(
// this.replicationService.getExecutions(this.executionId)
// )
// .then(executions => {
// console.log(executions);
// })
// .catch(error => {
// this.errorHandler.error(error);
// });
// }
// }
stopJob() {
this.stopOnGoing = true;
this.replicationService.stopJobs(this.executionId)
.subscribe(res => {
this.stopOnGoing = false;
// this.getExecutions();
},
error => {
this.errorHandler.error(error);
});
}
viewLog(taskId: number | string): string {
return this.replicationService.getJobBaseUrl() + "/" + this.executionId + "/tasks/" + taskId + "/log";
}
getTasks(): void {
this.replicationService.getReplicationTasks(this.executionId)
.subscribe(tasks => {
this.tasks = tasks.map(x => Object.assign({}, x));
},
error => {
this.errorHandler.error(error);
});
}
onBack(): void {
this.router.navigate(["harbor", "replications"]);
}
openFilter(isOpen: boolean): void {
if (isOpen) {
this.isOpenFilterTag = true;
} else {
this.isOpenFilterTag = false;
}
}
}

View File

@ -23,7 +23,7 @@
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 jobList" [hidden]='hiddenJobList'>
<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_JOBS' | translate}}</h5>
<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)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filterEvt)="doSearchJobs($event)"
@ -46,34 +46,31 @@
</div>
</div>
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid [clrDgLoading]="jobsLoading" (clrDgRefresh)="clrLoadJobs($event)">
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="jobsLoading" (clrDgRefresh)="clrLoadJobs($event)">
<clr-dg-action-bar>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!(jobs && jobs.length>0) || isStopOnGoing"
(click)="stopJobs()">{{'REPLICATION.STOPJOB' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!(jobs && jobs.length>0) || isStopOnGoing || !selectedRow.length"
(click)="openStopExecutionsDialog(selectedRow)">{{'REPLICATION.STOPJOB' | translate}}</button>
</div>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'repository'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'id'">{{'REPLICATION.ID' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'trigger'">{{'REPLICATION.REPLICATION_TRIGGER' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="updateTimeComparator">{{'REPLICATION.UPDATE_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.DURATION' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.SUCCESS_RATE' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPLICATION.JOB_PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let j of jobs">
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
<clr-dg-cell>{{j.status}}</clr-dg-cell>
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
<clr-dg-row *ngFor="let j of jobs" [clrDgItem]="j">
<clr-dg-cell>
<span *ngIf="j.status=='pending'; else elseBlock" class="label">{{'REPLICATION.NO_LOGS' | translate}}</span>
<ng-template #elseBlock>
<a target="_blank" [href]="viewLog(j.id)">
<clr-icon shape="list"></clr-icon>
</a>
</ng-template>
<a href="javascript:void(0)" (click)="goToLink(j.id)">{{j.id}}</a>
</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>
{{'90%'}}
</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>
@ -86,4 +83,5 @@
<hbr-create-edit-rule *ngIf="isSystemAdmin" [withAdmiral]="withAdmiral" [projectId]="projectId" [projectName]="projectName"
(goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
<confirmation-dialog #StopConfirmDialog (confirmAction)="confirmStop($event)"></confirmation-dialog>
</div>

View File

@ -22,6 +22,7 @@ import {ProjectDefaultService, ProjectService} from "../service/project.service"
import {OperationService} from "../operation/operation.service";
import {FilterLabelComponent} from "../create-edit-rule/filter-label.component";
import {LabelPieceComponent} from "../label-piece/label-piece.component";
import { RouterTestingModule } from '@angular/router/testing';
describe('Replication Component (inline template)', () => {
@ -208,14 +209,15 @@ describe('Replication Component (inline template)', () => {
let config: IServiceConfig = {
replicationRuleEndpoint: '/api/policies/replication/testing',
replicationJobEndpoint: '/api/jobs/replication/testing'
replicationBaseEndpoint: '/api/replication/executions/testing'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
NoopAnimationsModule
NoopAnimationsModule,
RouterTestingModule
],
declarations: [
ReplicationComponent,
@ -253,7 +255,7 @@ describe('Replication Component (inline template)', () => {
endpointService = fixtureCreate.debugElement.injector.get(EndpointService);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob));
spyJobs = spyOn(replicationService, 'getExecutions').and.returnValues(Promise.resolve(mockJob));
spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints));

View File

@ -21,8 +21,8 @@ import {
EventEmitter
} from "@angular/core";
import { Comparator, State } from "../service/interface";
import { Subscription, forkJoin, timer} from "rxjs";
import { Subscription, forkJoin, timer, throwError} from "rxjs";
import { finalize, catchError, map } from "rxjs/operators";
import { TranslateService } from "@ngx-translate/core";
@ -58,6 +58,8 @@ import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation
import {operateChanges, OperationState, OperateInfo} from "../operation/operate";
import {OperationService} from "../operation/operation.service";
import { Router } from "@angular/router";
const ruleStatus: { [key: string]: any } = [
{ key: "all", description: "REPLICATION.ALL_STATUS" },
{ key: "1", description: "REPLICATION.ENABLED" },
@ -124,6 +126,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
changedRules: ReplicationRule[];
selectedRow: ReplicationJobItem[] = [];
rules: ReplicationRule[];
loading: boolean;
isStopOnGoing: boolean;
@ -144,12 +147,15 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@ViewChild("replicationConfirmDialog")
replicationConfirmDialog: ConfirmationDialogComponent;
@ViewChild("StopConfirmDialog")
StopConfirmDialog: ConfirmationDialogComponent;
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("creation_time", "date");
>("start_time", "date");
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("update_time", "date");
>("end_time", "date");
// Server driven pagination
currentPage: number = 1;
@ -160,6 +166,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
timerDelay: Subscription;
constructor(
private router: Router,
private errorHandler: ErrorHandler,
private replicationService: ReplicationService,
private operationService: OperationService,
@ -197,6 +204,11 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.goToRegistry.emit();
}
goToLink(exeId: number): void {
let linkUrl = ["harbor", "replications", exeId, "tasks"];
this.router.navigate(linkUrl);
}
// Server driven data loading
clrLoadJobs(state: State): void {
if (!state || !state.page || !this.search.ruleId) {
@ -237,7 +249,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.jobsLoading = false;
toPromise<ReplicationJob>(
this.replicationService.getJobs(this.search.ruleId, params)
this.replicationService.getExecutions(this.search.ruleId, params)
)
.then(response => {
this.totalCount = response.metadata.xTotalCount;
@ -394,18 +406,68 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.hiddenJobList = true;
}
stopJobs() {
if (this.jobs && this.jobs.length) {
this.isStopOnGoing = true;
toPromise(this.replicationService.stopJobs(this.jobs[0].policy_id))
.then(res => {
this.refreshJobs();
this.isStopOnGoing = false;
})
.catch(error => this.errorHandler.error(error));
openStopExecutionsDialog(targets: ReplicationJobItem[]) {
let ExecutionId = targets.map(robot => robot.id).join(",");
let StopExecutionsMessage = new ConfirmationMessage(
"REPLICATION.STOP_TITLE",
"REPLICATION.STOP_SUMMARY",
ExecutionId,
targets,
ConfirmationTargets.STOP_EXECUTIONS,
ConfirmationButtons.STOP_CANCEL
);
this.StopConfirmDialog.open(StopExecutionsMessage);
}
confirmStop(message: ConfirmationAcknowledgement) {
if (
message &&
message.state === ConfirmationState.CONFIRMED &&
message.source === ConfirmationTargets.STOP_EXECUTIONS
) {
this.StopExecutions(message.data);
}
}
StopExecutions(targets: ReplicationJobItem[]): void {
if (targets && targets.length < 1) {
return;
}
this.isStopOnGoing = true;
if (this.jobs && this.jobs.length) {
let ExecutionsStop$ = targets.map(target => this.StopOperate(target));
forkJoin(ExecutionsStop$)
.pipe(
catchError(err => throwError(err)),
finalize(() => {
this.refreshJobs();
this.isStopOnGoing = false;
this.selectedRow = [];
})
)
.subscribe(() => { });
}
}
StopOperate(targets: ReplicationJobItem): any {
let operMessage = new OperateInfo();
operMessage.name = "OPERATION.STOP_EXECUTIONS";
operMessage.data.id = targets.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = targets.id;
this.operationService.publishInfo(operMessage);
return this.replicationService
.stopJobs(targets.id)
.pipe(
map(
() => operateChanges(operMessage, OperationState.success),
err => operateChanges(operMessage, OperationState.failure, err)
)
);
}
reloadRules(isReady: boolean) {
if (isReady) {
this.search.ruleName = "";
@ -453,8 +515,4 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.search.endTimestamp = toTimestamp;
this.loadFirstPage();
}
viewLog(jobId: number | string): string {
return this.replicationService.getJobBaseUrl() + "/" + jobId + "/log";
}
}

View File

@ -66,16 +66,6 @@ export interface IServiceConfig {
*/
replicationRuleEndpoint?: string;
/**
* The base endpoint of the service used to handle the replication jobs.
*
*
* * {string}
* @memberOf IServiceConfig
*/
replicationJobEndpoint?: string;
/**
* The base endpoint of the service used to handle vulnerability scanning.
*

View File

@ -132,7 +132,7 @@ export class EndpointDefaultService extends EndpointService {
super();
this._endpointUrl = config.targetBaseEndpoint
? config.targetBaseEndpoint
: "/api/targets";
: "/api/registries";
}
public getEndpoints(

View File

@ -103,14 +103,24 @@ export interface ReplicationRule extends Base {
filters: Filter[];
replicate_existing_image_now?: boolean;
replicate_deletion?: boolean;
// id?: number;
// name: string;
// description: string;
// src_registry_id: number;
// src_namespaces: [];
// dest_registry_id: number;
// dest_namespace: string;
// trigger: Trigger;
// filter: Filter[];
// deletion: boolean;
// override: boolean;
// enabled: boolean;
}
export class Filter {
kind: string;
pattern: string;
constructor(kind: string, pattern: string) {
this.kind = kind;
this.pattern = pattern;
type: string;
constructor(type: string) {
this.type = type;
}
}
@ -146,6 +156,7 @@ export interface ReplicationJob {
*/
export interface ReplicationJobItem extends Base {
[key: string]: any | any[];
id: number;
status: string;
repository: string;
policy_id: number;
@ -153,6 +164,22 @@ export interface ReplicationJobItem extends Base {
tags: string;
}
/**
* Interface for replication tasks item.
*
**
* interface ReplicationTasks
*/
export interface ReplicationTasks extends Base {
[key: string]: any | any[];
id: number;
execution_id: number;
resource_type: string;
src_resource: string;
dst_resource: string;
job_id: number;
status: string;
}
/**
* Interface for storing metadata of response.
*

View File

@ -6,7 +6,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
describe('JobLogService', () => {
const mockConfig: IServiceConfig = {
replicationJobEndpoint: "/api/jobs/replication/testing",
replicationBaseEndpoint: "/api/replication/executions/testing",
scanJobEndpoint: "/api/jobs/scan/testing"
};
@ -33,7 +33,7 @@ describe('JobLogService', () => {
it('should be initialized', inject([JobLogDefaultService], (service: JobLogService) => {
expect(service).toBeTruthy();
expect(config.replicationJobEndpoint).toEqual("/api/jobs/replication/testing");
expect(config.replicationBaseEndpoint).toEqual("/api/replication/executions/testing");
expect(config.scanJobEndpoint).toEqual("/api/jobs/scan/testing");
}));
});

View File

@ -47,9 +47,9 @@ export class JobLogDefaultService extends JobLogService {
@Inject(SERVICE_CONFIG) config: IServiceConfig
) {
super();
this._replicationJobBaseUrl = config.replicationJobEndpoint
? config.replicationJobEndpoint
: "/api/jobs/replication";
this._replicationJobBaseUrl = config.replicationBaseEndpoint
? config.replicationBaseEndpoint
: "/api/replication/executions";
this._scanningJobBaseUrl = config.scanJobEndpoint
? config.scanJobEndpoint
: "/api/jobs/scan";

View File

@ -7,7 +7,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
describe('ReplicationService', () => {
const mockConfig: IServiceConfig = {
replicationRuleEndpoint: "/api/policies/replication/testing",
replicationJobEndpoint: "/api/jobs/replication/testing"
replicationBaseEndpoint: "/api/replication/executions/testing"
};
let config: IServiceConfig;
@ -38,6 +38,6 @@ describe('ReplicationService', () => {
it('should inject the right config', () => {
expect(config).toBeTruthy();
expect(config.replicationRuleEndpoint).toEqual("/api/policies/replication/testing");
expect(config.replicationJobEndpoint).toEqual("/api/jobs/replication/testing");
expect(config.replicationBaseEndpoint).toEqual("/api/replication/executions/testing");
});
});

View File

@ -1,6 +1,5 @@
import { Http } from "@angular/http";
import { Injectable, Inject } from "@angular/core";
import { Observable } from "rxjs";
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import {
@ -11,10 +10,12 @@ import {
import {
ReplicationJob,
ReplicationRule,
ReplicationJobItem
ReplicationJobItem,
ReplicationTasks
} from "./interface";
import { RequestQueryParams } from "./RequestQueryParams";
import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError } from "rxjs";
/**
* Define the service methods to handle the replication (rule and job) related things.
*
@ -59,6 +60,18 @@ export abstract class ReplicationService {
ruleId: number | string
): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule;
/**
* Get the specified replication task.
*
* @abstract
* returns {(Observable<ReplicationRule>)}
*
* @memberOf ReplicationService
*/
abstract getReplicationTasks(
executionId: number | string
): Observable<ReplicationTasks>;
/**
* Create new replication rule.
*
@ -146,7 +159,7 @@ export abstract class ReplicationService {
*
* @memberOf ReplicationService
*/
abstract getJobs(
abstract getExecutions(
ruleId: number | string,
queryParams?: RequestQueryParams
): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob;
@ -165,7 +178,7 @@ export abstract class ReplicationService {
abstract stopJobs(
jobId: number | string
): Observable<string> | Promise<string> | string;
): Observable<string>;
abstract getJobBaseUrl(): string;
}
@ -180,7 +193,6 @@ export abstract class ReplicationService {
@Injectable()
export class ReplicationDefaultService extends ReplicationService {
_ruleBaseUrl: string;
_jobBaseUrl: string;
_replicateUrl: string;
constructor(
@ -190,13 +202,10 @@ export class ReplicationDefaultService extends ReplicationService {
super();
this._ruleBaseUrl = config.replicationRuleEndpoint
? config.replicationRuleEndpoint
: "/api/policies/replication";
this._jobBaseUrl = config.replicationJobEndpoint
? config.replicationJobEndpoint
: "/api/jobs/replication";
: "/api/replication/policies";
this._replicateUrl = config.replicationBaseEndpoint
? config.replicationBaseEndpoint
: "/api/replications";
: "/api/replication/executions";
}
// Private methods
@ -212,7 +221,7 @@ export class ReplicationDefaultService extends ReplicationService {
}
public getJobBaseUrl() {
return this._jobBaseUrl;
return this._replicateUrl;
}
public getReplicationRules(
@ -257,6 +266,19 @@ export class ReplicationDefaultService extends ReplicationService {
.catch(error => Promise.reject(error));
}
public getReplicationTasks(
executionId: number | string
): Observable<ReplicationTasks> {
if (!executionId) {
return observableThrowError("Bad argument");
}
let url: string = `${this._replicateUrl}/${executionId}/tasks`;
return this.http
.get(url, HTTP_GET_OPTIONS)
.pipe(map (response => response.json() as ReplicationTasks)
, catchError(error => observableThrowError(error)));
}
public createReplicationRule(
replicationRule: ReplicationRule
): Observable<any> | Promise<any> | any {
@ -352,7 +374,7 @@ export class ReplicationDefaultService extends ReplicationService {
.catch(error => Promise.reject(error));
}
public getJobs(
public getExecutions(
ruleId: number | string,
queryParams?: RequestQueryParams
): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob {
@ -366,7 +388,7 @@ export class ReplicationDefaultService extends ReplicationService {
queryParams.set("policy_id", "" + ruleId);
return this.http
.get(this._jobBaseUrl, buildHttpRequestOptions(queryParams))
.get(this._replicateUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => {
let result: ReplicationJob = {
@ -401,7 +423,7 @@ export class ReplicationDefaultService extends ReplicationService {
return Promise.reject("Bad argument");
}
let logUrl = `${this._jobBaseUrl}/${jobId}/log`;
let logUrl = `${this._replicateUrl}/${jobId}/log`;
return this.http
.get(logUrl, HTTP_GET_OPTIONS)
.toPromise()
@ -412,14 +434,16 @@ export class ReplicationDefaultService extends ReplicationService {
public stopJobs(
jobId: number | string
): Observable<any> | Promise<any> | any {
if (!jobId || jobId <= 0) {
return observableThrowError("Bad request argument.");
}
let requestUrl: string = `${this._replicateUrl}/${jobId}`;
return this.http
.put(
this._jobBaseUrl,
JSON.stringify({ policy_id: jobId, status: "stop" }),
requestUrl,
HTTP_JSON_OPTIONS
)
.toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
.pipe(map(response => response)
, catchError(error => observableThrowError(error)));
}
}

View File

@ -43,7 +43,8 @@ export const enum ConfirmationTargets {
CONFIG_ROUTE,
CONFIG_TAB,
HELM_CHART,
HELM_CHART_VERSION
HELM_CHART_VERSION,
STOP_EXECUTIONS
}
export const enum ActionType {
@ -69,7 +70,7 @@ export const enum ConfirmationState {
}
export const enum ConfirmationButtons {
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, REPLICATE_CANCEL
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, REPLICATE_CANCEL, STOP_CANCEL
}
export const LabelColor = [

View File

@ -30,6 +30,8 @@ import { ResetPasswordComponent } from './account/password-setting/reset-passwor
import { GroupComponent } from './group/group.component';
import { TotalReplicationPageComponent } from './replication/total-replication/total-replication-page.component';
import { ReplicationTasksPageComponent } from './replication/replication-tasks-page/replication-tasks-page.component';
import { DestinationPageComponent } from './replication/destination/destination-page.component';
import { ReplicationPageComponent } from './replication/replication-page.component';
@ -99,6 +101,12 @@ const harborRoutes: Routes = [
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard],
},
{
path: 'replications/:id/:tasks',
component: ReplicationTasksPageComponent,
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard],
},
{
path: 'tags/:id/:repo',
component: TagRepositoryComponent,

View File

@ -0,0 +1 @@
<replication-tasks [executionId]="executionId"></replication-tasks>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReplicationTasksPageComponent } from './replication-tasks-page.component';
describe('ReplicationTasksPageComponent', () => {
let component: ReplicationTasksPageComponent;
let fixture: ComponentFixture<ReplicationTasksPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ReplicationTasksPageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ReplicationTasksPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-replication-tasks-page',
templateUrl: './replication-tasks-page.component.html',
styleUrls: ['./replication-tasks-page.component.scss']
})
export class ReplicationTasksPageComponent implements OnInit {
executionId: string;
constructor(
private route: ActivatedRoute,
) { }
ngOnInit(): void {
this.executionId = this.route.snapshot.params["id"];
}
}

View File

@ -18,6 +18,7 @@ import { ReplicationManagementComponent } from './replication-management/replica
import { ReplicationPageComponent } from './replication-page.component';
import { TotalReplicationPageComponent } from './total-replication/total-replication-page.component';
import { DestinationPageComponent } from './destination/destination-page.component';
import { ReplicationTasksPageComponent } from './replication-tasks-page/replication-tasks-page.component';
import { SharedModule } from '../shared/shared.module';
import {ReactiveFormsModule} from "@angular/forms";
@ -32,11 +33,13 @@ import {ReactiveFormsModule} from "@angular/forms";
ReplicationPageComponent,
ReplicationManagementComponent,
TotalReplicationPageComponent,
ReplicationTasksPageComponent,
DestinationPageComponent,
],
exports: [
ReplicationPageComponent,
DestinationPageComponent,
ReplicationTasksPageComponent,
TotalReplicationPageComponent,
]
})

View File

@ -62,10 +62,9 @@ const uiLibConfig: IServiceConfig = {
systemInfoEndpoint: "/api/systeminfo",
repositoryBaseEndpoint: "/api/repositories",
logBaseEndpoint: "/api/logs",
targetBaseEndpoint: "/api/targets",
replicationBaseEndpoint: "/api/replications",
replicationRuleEndpoint: "/api/policies/replication",
replicationJobEndpoint: "/api/jobs/replication",
targetBaseEndpoint: "/api/registries",
replicationBaseEndpoint: "/api/replication/executions",
replicationRuleEndpoint: "/api/replication/policies",
vulnerabilityScanningBaseEndpoint: "/api/repositories",
projectPolicyEndpoint: "/api/projects/configs",
projectBaseEndpoint: "/api/projects",

View File

@ -17,6 +17,7 @@
"TITLE": "Sign Up"
},
"BUTTON": {
"STOP": "STOP",
"CANCEL": "CANCEL",
"OK": "OK",
"DELETE": "DELETE",
@ -330,6 +331,18 @@
"OF": "of"
},
"REPLICATION": {
"STOP_TITLE": "Confirm Stop Executions",
"STOP_SUMMARY": "Do you want to stop the executions {{param}}?",
"TASK_ID":"Task ID",
"RECOURCE_TYPE": "Recource Type",
"RECOURCE": "Recource",
"DESTINATION": "Destination",
"POLICY": "Policy",
"DURATION": "Duration",
"SUCCESS_RATE": "Success Rate",
"SUCCESS": "Success",
"FAILTURE": "Failture",
"IN_PROGRESS": "In Progress",
"REPLICATION_RULE": "Replication Rule",
"NEW_REPLICATION_RULE": "New Replication Rule",
"ENDPOINTS": "Endpoints",
@ -353,13 +366,16 @@
"TESTING_CONNECTION": "Testing Connection...",
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
"ID":"ID",
"NAME": "Name",
"PROJECT": "Project",
"NAME_IS_REQUIRED": "Name is required.",
"DESCRIPTION": "Description",
"ENABLE": "Enable",
"DISABLE": "Disable",
"DESTINATION_NAME": "Endpoint Name",
"REPLICATION_MODE": "Replication Mode",
"SRC_NAMESPACE": "Source registry:Namespace",
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
"LAST_REPLICATION":"Last Replication",
"DESTINATION_NAME_IS_REQUIRED": "Endpoint name is required.",
"NEW_DESTINATION": "New Endpoint",
"DESTINATION_URL": "Endpoint URL",
@ -371,8 +387,9 @@
"DISABLED": "Disabled",
"LAST_START_TIME": "Last Start Time",
"ACTIVATION": "Activation",
"REPLICATION_JOBS": "Replication Jobs",
"STOPJOB": "Stop Jobs",
"REPLICATION_EXECUTION": "Execution",
"REPLICATION_EXECUTIONS": "Executions",
"STOPJOB": "Stop",
"ALL": "All",
"PENDING": "Pending",
"RUNNING": "Running",
@ -384,9 +401,10 @@
"SIMPLE": "Simple",
"ADVANCED": "Advanced",
"STATUS": "Status",
"OPERATION": "Operation",
"REPLICATION_TRIGGER": "Trigger",
"CREATION_TIME": "Start Time",
"UPDATE_TIME": "Update Time",
"END_TIME": "End Time",
"LOGS": "Logs",
"OF": "of",
"ITEMS": "items",
@ -840,6 +858,7 @@
"ALL": "All",
"RUNNING": "Running",
"FAILED": "Failed",
"STOP_EXECUTIONS": "Stop Execution",
"DELETE_PROJECT": "Delete project",
"DELETE_REPO": "Delete repository",
"DELETE_TAG": "Delete tag",

View File

@ -17,6 +17,7 @@
"TITLE": "Registrarse"
},
"BUTTON": {
"STOP": "STOP",
"CANCEL": "CANCELAR",
"OK": "OK",
"DELETE": "ELIMINAR",
@ -329,6 +330,20 @@
"OF": "of"
},
"REPLICATION": {
"STOP_TITLE": "Confirme Stop Executions",
"STOP_SUMMARY": "De que desea detener las ejecuciones {{param}}?",
"TASK_ID":"Task ID",
"RECOURCE_TYPE": "Recource Type",
"RECOURCE": "Recource",
"DESTINATION": "Destination",
"POLICY": "Policy",
"DURATION": "Duration",
"SUCCESS_RATE": "Success Rate",
"SUCCESS": "Success",
"FAILTURE": "Failture",
"IN_PROGRESS": "In Progress",
"STOP_EXECUTIONS": "Stop Execution",
"ID":"ID",
"REPLICATION_RULE": "Reglas de Replicación",
"NEW_REPLICATION_RULE": "Nueva Regla de Replicación",
"ENDPOINTS": "Endpoints",
@ -358,7 +373,10 @@
"DESCRIPTION": "Descripción",
"ENABLE": "Activar",
"DISABLE": "Desactivar",
"DESTINATION_NAME": "Nombre del Endpoint",
"REPLICATION_MODE": "Replication Mode",
"SRC_NAMESPACE": "Source registry:Namespace",
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
"LAST_REPLICATION":"Last Replication",
"DESTINATION_NAME_IS_REQUIRED": "El nombre del endpoint es obligatorio.",
"NEW_DESTINATION": "Nuevo Endpoint",
"DESTINATION_URL": "URL del Endpoint",
@ -370,8 +388,10 @@
"DISABLED": "Desactivado",
"LAST_START_TIME": "Última Fecha de Inicio",
"ACTIVATION": "Activación",
"REPLICATION_JOBS": "Trabajos de Replicación",
"STOPJOB": "Stop Jobs",
"REPLICATION_EXECUTION": "Trabajo de Replicación",
"REPLICATION_EXECUTIONS": "Trabajos de Replicación",
"END_TIME": "End Time",
"STOPJOB": "Stop",
"ALL": "Todos",
"PENDING": "Pendiente",
"RUNNING": "Ejecutando",

View File

@ -17,6 +17,7 @@
"TITLE": "S'inscrire"
},
"BUTTON": {
"STOP": "STOP",
"CANCEL": "ANNULER",
"OK": "OK",
"DELETE": "SUPPRIMER",
@ -314,6 +315,20 @@
"OF": "de"
},
"REPLICATION": {
"STOP_TITLE": "Confirmer arrêter les exécutions",
"STOP_SUMMARY": "Voulez-vous arrêter les exécutions {{param}}?",
"TASK_ID":"Task ID",
"RECOURCE_TYPE": "Recource Type",
"RECOURCE": "Recource",
"DESTINATION": "Destination",
"POLICY": "Policy",
"DURATION": "Duration",
"SUCCESS_RATE": "Success Rate",
"SUCCESS": "Success",
"FAILTURE": "Failture",
"IN_PROGRESS": "In Progress",
"STOP_EXECUTIONS": "Stop Execution",
"ID":"ID",
"REPLICATION_RULE": "Règle de Réplication",
"NEW_REPLICATION_RULE": "Nouvelle Règle de Réplication",
"ENDPOINTS": "Points finaux",
@ -340,7 +355,10 @@
"DESCRIPTION": "Description",
"ENABLE": "Activer",
"DISABLE": "Désactiver",
"DESTINATION_NAME": "Nom du Point Final",
"REPLICATION_MODE": "Replication Mode",
"SRC_NAMESPACE": "Source registry:Namespace",
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
"LAST_REPLICATION":"Last Replication",
"DESTINATION_NAME_IS_REQUIRED": "Le nom du Point Final est obligatoire.",
"NEW_DESTINATION": "Nouveau Point Final",
"DESTINATION_URL": "URL du Point Final",
@ -352,8 +370,10 @@
"DISABLED": "Désactivé",
"LAST_START_TIME": "Dernière heure de démarrage",
"ACTIVATION": "Activation",
"REPLICATION_JOBS": "Travaux de Réplication",
"REPLICATION_EXECUTION": "Travaux de Réplication",
"REPLICATION_EXECUTIONS": "Travaux de Réplication",
"ALL": "Tous",
"END_TIME": "End Time",
"PENDING": "En attente",
"RUNNING": "En fonctionnement",
"ERROR": "Erreur",

View File

@ -17,6 +17,7 @@
"TITLE": "Registrar-se"
},
"BUTTON": {
"STOP": "STOP",
"CANCEL": "CANCELAR",
"OK": "OK",
"DELETE": "DELETAR",
@ -328,6 +329,20 @@
"OF": "de"
},
"REPLICATION": {
"STOP_TITLE": "Confirme as execuções de parada",
"STOP_SUMMARY": "Você quer parar as execuções? {{param}}?",
"TASK_ID":"Task ID",
"RECOURCE_TYPE": "Recource Type",
"RECOURCE": "Recource",
"DESTINATION": "Destination",
"POLICY": "Policy",
"DURATION": "Duration",
"SUCCESS_RATE": "Success Rate",
"SUCCESS": "Success",
"FAILTURE": "Failture",
"IN_PROGRESS": "In Progress",
"STOP_EXECUTIONS": "Stop Execution",
"ID":"ID",
"REPLICATION_RULE": "Regra de replicação",
"NEW_REPLICATION_RULE": "Nova regra de replicação",
"ENDPOINTS": "Endpoints",
@ -357,7 +372,10 @@
"DESCRIPTION": "Descrição",
"ENABLE": "Habilitar",
"DISABLE": "Desabilitar",
"DESTINATION_NAME": "Nome do Endpoint",
"REPLICATION_MODE": "Replication Mode",
"SRC_NAMESPACE": "Source registry:Namespace",
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
"LAST_REPLICATION":"Last Replication",
"DESTINATION_NAME_IS_REQUIRED": "Nome do Endpoint é obrigatório.",
"NEW_DESTINATION": "Novo Endpoint",
"DESTINATION_URL": "URL do Endpoint",
@ -369,8 +387,10 @@
"DISABLED": "Desabilitado",
"LAST_START_TIME": "Ultima hora de início",
"ACTIVATION": "Ativação",
"REPLICATION_JOBS": "Tarefas de Replicação",
"STOPJOB": "Parar tarefas",
"REPLICATION_EXECUTION": "ExecTarefa de Replicaçãoution",
"REPLICATION_EXECUTIONS": "Tarefas de Replicação",
"STOPJOB": "Parar",
"END_TIME": "End Time",
"ALL": "Todas",
"PENDING": "Pendentes",
"RUNNING": "Executando",

View File

@ -17,6 +17,7 @@
"TITLE": "注册"
},
"BUTTON": {
"STOP": "停止",
"CANCEL": "取消",
"OK": "确定",
"DELETE": "删除",
@ -329,6 +330,20 @@
"OF": "共计"
},
"REPLICATION": {
"STOP_TITLE": "确认停止任务",
"STOP_SUMMARY": "确认停止任务{{param}}?",
"TASK_ID":"任务ID",
"RECOURCE_TYPE": "源类型",
"RECOURCE": "源",
"DESTINATION": "目标",
"POLICY": "政策",
"DURATION": "到期时间",
"SUCCESS_RATE": "成功百分比",
"SUCCESS": "成功",
"FAILTURE": "失败",
"IN_PROGRESS": "进行中",
"STOP_EXECUTIONS": "停止任务",
"ID":"ID",
"REPLICATION_RULE": "复制规则",
"NEW_REPLICATION_RULE": "新建规则",
"ENDPOINTS": "目标",
@ -358,7 +373,10 @@
"DESCRIPTION": "描述",
"ENABLE": "启用",
"DISABLE": "停用",
"DESTINATION_NAME": "目标名",
"REPLICATION_MODE": "复制模式",
"SRC_NAMESPACE": "源仓库:命名空间",
"DESTINATION_NAMESPACE": "目标仓库:命名空间",
"LAST_REPLICATION":"最后一次复制",
"DESTINATION_NAME_IS_REQUIRED": "目标名称为必填项。",
"NEW_DESTINATION": "创建目标",
"DESTINATION_URL": "目标URL",
@ -370,7 +388,8 @@
"DISABLED": "停用",
"LAST_START_TIME": "上次起始时间",
"ACTIVATION": "活动状态",
"REPLICATION_JOBS": "复制任务",
"REPLICATION_EXECUTION": "复制任务",
"REPLICATION_EXECUTIONS": "复制任务",
"STOPJOB": "停止任务",
"ALL": "全部",
"PENDING": "挂起",
@ -386,6 +405,7 @@
"OPERATION": "操作",
"CREATION_TIME": "创建时间",
"UPDATE_TIME": "更新时间",
"END_TIME": "结束时间",
"LOGS": "日志",
"OF": "共计",
"ITEMS": "条记录",