Modify replication list to batch delection mode and add admin privilege to project replication module

Modity text about new replication rule
This commit is contained in:
pfh 2018-01-12 13:06:24 +08:00
parent a214084370
commit 78dcdc409f
27 changed files with 356 additions and 163 deletions

View File

@ -30,12 +30,17 @@ export const CONFIRMATION_DIALOG_TEMPLATE: string = `
</ng-template>
<ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="delete()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
</div>
</clr-modal>
`;

View File

@ -40,7 +40,6 @@ export class ConfirmationDialogComponent {
@Input() batchInfors: BatchInfo[] = [];
isDelete: boolean = false;
constructor(
private translate: TranslateService) {}
@ -98,7 +97,7 @@ export class ConfirmationDialogComponent {
this.close();
}
delete(): void {
operate(): void {
if (!this.message){//Inproper condition
this.close();
return;

View File

@ -1,12 +1,12 @@
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<div style="padding-bottom: 15px;">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" (clrDgSingleSelectedChange)="selectedChange()">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
<clr-dg-action-bar style="height:24px;">
<div class="btn-group" *ngIf="opereateAvailable">
<div class="btn-group" *ngIf="opereateAvailable || isSystemAdmin">
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()">{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="editRule(selectedRow)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)">{{'REPLICATION.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="selectedRow.length !== 1" (click)="editRule(selectedRow)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="selectedRow.length == 0" (click)="deleteRule(selectedRow)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="selectedRow.length == 0" (click)="replicateRule(selectedRow)">{{'REPLICATION.REPLICATE' | translate}}</button>
</div>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
@ -15,14 +15,14 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<clr-dg-column [clrDgField]="'targets'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'trigger'">{{'REPLICATION.SCHEDULE' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let p of changedRules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
<clr-dg-cell>{{p.name}}</clr-dg-cell>
<clr-dg-cell *ngIf="!projectScope">
<clr-dg-row *ngFor="let p of changedRules" [clrDgItem]="p" [style.backgroundColor]="(projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
<clr-dg-cell (click)="selectRule(p)">{{p.name}}</clr-dg-cell>
<clr-dg-cell *ngIf="!projectScope" (click)="selectRule(p)">
<a href="javascript:void(0)" (click)="redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
</clr-dg-cell>
<clr-dg-cell>{{p.description ? p.description : '-'}}</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-cell (click)="selectRule(p)">{{p.description ? p.description : '-'}}</clr-dg-cell>
<clr-dg-cell (click)="selectRule(p)">{{p.targets?.length>0 ? p.targets[0].name : ''}}</clr-dg-cell>
<clr-dg-cell (click)="selectRule(p)">{{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

@ -55,6 +55,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
nullTime: string = '0001-01-01T00:00:00Z';
@Input() projectId: number;
@Input() isSystemAdmin: boolean;
@Input() selectedId: number | string;
@Input() withReplicationJob: boolean;
@Input() readonly: boolean;
@ -67,16 +68,17 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
@Output() toggleOne = new EventEmitter<ReplicationRule>();
@Output() redirect = new EventEmitter<ReplicationRule>();
@Output() openNewRule = new EventEmitter<any>();
@Output() replicateManual = new EventEmitter<ReplicationRule>();
@Output() replicateManual = new EventEmitter<ReplicationRule[]>();
@Output() hasJobs = new EventEmitter<boolean>();
projectScope: boolean = false;
rules: ReplicationRule[];
changedRules: ReplicationRule[];
ruleName: string;
canDeleteRule: boolean;
canDeleteRuleList: boolean[] = [];
selectedRow: ReplicationRule;
selectedRow: ReplicationRule[] = [];
batchDelectionInfos: BatchInfo[] = [];
@ViewChild('toggleConfirmDialog')
@ -100,7 +102,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
return !this.readonly && !this.projectId ? true : false;
}
ngOnInit(): void {
//Global scope
if (!this.projectScope) {
@ -122,13 +123,9 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
}
}
selectedChange(): void {
let hnd = setInterval(() => this.ref.markForCheck(), 200);
setTimeout(() => clearInterval(hnd), 2000);
}
retrieveRules(ruleName: string = ''): void {
this.loading = true;
this.selectedRow = [];
toPromise<ReplicationRule[]>(this.replicationService
.getReplicationRules(this.projectId, ruleName))
.then(rules => {
@ -176,38 +173,30 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
}
}
replicateRule(rules: ReplicationRule[]): void {
this.replicateManual.emit(rules);
}
deletionConfirm(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.POLICY &&
message.state === ConfirmationState.CONFIRMED) {
let rule: ReplicationRule = message.data;
toPromise<any>(this.replicationService
.deleteReplicationRule(rule.id))
.then(() => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
this.batchDelectionInfos[0] = BathInfoChanges(this.batchDelectionInfos[0], res);
});
this.reload.emit(true);
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => {
this.batchDelectionInfos[0] = BathInfoChanges(this.batchDelectionInfos[0], res[0], false, true, res[1]);
});
} else {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
this.batchDelectionInfos[0] = BathInfoChanges(this.batchDelectionInfos[0], res, false, true);
});
}
});
this.deleteOpe(message.data);
}
}
selectedChange(): void {
if (this.selectedRow.length !== 0) {
this.hasJobs.emit(false);
}
let hnd = setInterval(() => this.ref.markForCheck(), 200);
setTimeout(() => clearInterval(hnd), 2000);
}
selectRule(rule: ReplicationRule): void {
this.selectedId = rule.id || '';
this.selectedRow = [];
this.selectOne.emit(rule);
this.hasJobs.emit(true);
}
redirectTo(rule: ReplicationRule): void {
@ -218,12 +207,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
this.openNewRule.emit();
}
editRule(rules: ReplicationRule) {
this.editOne.emit(rules);
}
replicateRule(rule: ReplicationRule) {
this.replicateManual.emit(rule);
editRule(rules: ReplicationRule[]) {
this.editOne.emit(rules[0]);
}
toggleRule(rule: ReplicationRule) {
@ -237,12 +222,12 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
this.toggleConfirmDialog.open(toggleConfirmMessage);
}
jobList(): Promise<void> {
jobList(id: string | number): Promise<void> {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;
this.canDeleteRuleList = [];
let count: number = 0;
return toPromise<ReplicationJob>(this.replicationService
.getJobs(this.selectedId))
.getJobs(id))
.then(response => {
ruleData = response.data;
if (ruleData.length) {
@ -252,37 +237,82 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
}
});
}
this.canDeleteRule = count > 0 ? false : true;
let canDeleteRule: boolean = count > 0 ? false : true;
this.canDeleteRuleList.push(canDeleteRule);
})
.catch(error => this.errorHandler.error(error));
}
deleteRule(rule: ReplicationRule) {
this.jobList().then(() => {
let deletionMessage: ConfirmationMessage;
if (!this.canDeleteRule) {
deletionMessage = new ConfirmationMessage(
'REPLICATION.DELETION_TITLE_FAILURE',
'REPLICATION.DELETION_SUMMARY_FAILURE',
rule.name || '',
rule,
ConfirmationTargets.POLICY,
ConfirmationButtons.CLOSE);
} else {
deleteRule(rules: ReplicationRule[]) {
if (rules && rules.length) {
let nameArr: string[] = [];
this.batchDelectionInfos = [];
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = rule.name;
rules.forEach(data => {
nameArr.push(data.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = data.name;
this.batchDelectionInfos.push(initBatchMessage);
deletionMessage = new ConfirmationMessage(
});
let deletionMessage = new ConfirmationMessage(
'REPLICATION.DELETION_TITLE',
'REPLICATION.DELETION_SUMMARY',
rule.name || '',
rule,
nameArr.join(',') || '',
rules,
ConfirmationTargets.POLICY,
ConfirmationButtons.DELETE_CANCEL);
}
this.deletionConfirmDialog.open(deletionMessage);
}
}
deleteOpe(rules: ReplicationRule[]) {
if (rules && rules.length) {
let promiseLists: any[] = [];
let promiseJobLists: any[] = [];
rules.forEach(rule => {
promiseJobLists.push(this.jobList(rule.id));
})
Promise.all(promiseJobLists).then(items => {
this.canDeleteRuleList.forEach((item, index) => {
if (!item) {
let findedList = this.batchDelectionInfos.find(data => data.name === rules[index].name);
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPLICATION.DELETION_SUMMARY_FAILURE')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
} else {
promiseLists.push(this.delOperate(+rules[index].id, rules[index].name));
}
});
Promise.all(promiseLists).then(item => {
this.selectedRow = [];
this.reload.emit(true);
let hnd = setInterval(() => this.ref.markForCheck(), 200);
setTimeout(() => clearInterval(hnd), 2000);
});
});
}
}
delOperate(ruleId: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<any>(this.replicationService
.deleteReplicationRule(ruleId))
.then(() => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => findedList = BathInfoChanges(findedList, res));
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
} else {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
}
});
}
}

View File

@ -11,8 +11,9 @@ export const REPLICATION_TEMPLATE: string = `
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event) (hasJobs)="hasJobList($event)" (selectOne)="selectOneRule($event)" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" [hidden]="!hasJobs">
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="row flex-items-xs-between" style="height:60px;">
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
@ -37,7 +38,11 @@ export const REPLICATION_TEMPLATE: string = `
</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 [clrDgLoading]="jobsLoading" (clrDgRefresh)="clrLoadJobs($event)"><clr-dg-action-bar>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(jobs && jobs.length>0)" (click)="stopJobs()">{{'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>
@ -66,5 +71,7 @@ export const REPLICATION_TEMPLATE: string = `
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<job-log-viewer #replicationLogViewer></job-log-viewer>
<confirmation-dialog #replicationConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</div>`;

View File

@ -43,6 +43,11 @@ import { JobLogViewerComponent } from '../job-log-viewer/index';
import { State } from "clarity-angular";
import {Observable} from "rxjs/Observable";
import {Subscription} from "rxjs/Subscription";
import {ConfirmationTargets, ConfirmationButtons, ConfirmationState} from "../shared/shared.const";
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message";
const ruleStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
@ -84,6 +89,7 @@ export class SearchOption {
export class ReplicationComponent implements OnInit, OnDestroy {
@Input() projectId: number | string;
@Input() isSystemAdmin: boolean;
@Input() withReplicationJob: boolean;
@Input() readonly: boolean;
@ -101,11 +107,13 @@ export class ReplicationComponent implements OnInit, OnDestroy {
changedRules: ReplicationRule[];
initSelectedId: number | string;
hasJobs: boolean;
rules: ReplicationRule[];
loading: boolean;
jobs: ReplicationJobItem[];
batchDelectionInfos: BatchInfo[] = [];
toggleJobSearchOption = optionalSearch;
currentJobSearchOption: number;
@ -119,6 +127,9 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@ViewChild("replicationLogViewer")
replicationLogViewer: JobLogViewerComponent;
@ViewChild('replicationConfirmDialog')
replicationConfirmDialog: ConfirmationDialogComponent;
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date');
@ -259,13 +270,61 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
}
replicateManualRule(rule: ReplicationRule): void {
toPromise<any>(this.replicationService.replicateRule(rule.id))
.then(response => {
this.refreshJobs();
})
.catch(error => this.errorHandler.error(error));
replicateManualRule(rules: ReplicationRule[]) {
if (rules && rules.length) {
let nameArr: string[] = [];
this.batchDelectionInfos = [];
rules.forEach(rule => {
nameArr.push(rule.name);
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let replicationMessage = new ConfirmationMessage(
'REPLICATION.REPLICATION_TITLE',
'REPLICATION.REPLICATION_SUMMARY',
nameArr.join(', ') || '',
rules,
ConfirmationTargets.TARGET,
ConfirmationButtons.REPLICATE_CANCEL);
this.replicationConfirmDialog.open(replicationMessage);
}
}
confirmReplication(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let rules: Endpoint[] = message.data;
if (rules && rules.length) {
let promiseLists: any[] = [];
rules.forEach(rule => {
this.replicationOperate(+rule.id, rule.name);
})
Promise.all(promiseLists).then((item) => {
this.listReplicationRule.retrieveRules();
this.refreshJobs();
});
}
}
}
replicationOperate(ruleId: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<any>(this.replicationService.replicateRule(ruleId))
.then(response => {
this.translateService.get('BATCH.REPLICATE_SUCCESS')
.subscribe(res => findedList = BathInfoChanges(findedList, res));
})
.catch(error => {
this.translateService.get('BATCH.REPLICATE_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
});
}
customRedirect(rule: ReplicationRule) {
this.redirect.emit(rule);
@ -295,6 +354,14 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.loadFirstPage();
}
stopJobs() {
if (this.jobs && this.jobs.length) {
toPromise(this.replicationService.stopJobs(this.jobs[0].policy_id))
.then(res => {this.refreshJobs(); })
.catch(error => this.errorHandler.error(error));
}
}
reloadRules(isReady: boolean) {
if (isReady) {
this.search.ruleName = '';
@ -306,6 +373,13 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.listReplicationRule.retrieveRules();
}
hasJobList(hasJob: boolean): void {
this.hasJobs = hasJob;
if (this.hasJobs) {
this.refreshJobs();
}
}
refreshJobs() {
this.search.repoName = "";
this.search.startTimestamp = "";

View File

@ -156,8 +156,9 @@ export class RepositoryListviewComponent implements OnChanges, OnInit {
delOperate(repoName: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === repoName);
if (this.signedCon[repoName].length !== 0) {
this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED').subscribe(res => {
findedList.status = res;
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
} else {
return toPromise<number>(this.repositoryService

View File

@ -127,6 +127,8 @@ export abstract class ReplicationService {
* @memberof ReplicationService
*/
abstract getJobLog(jobId: number | string): Observable<string> | Promise<string> | string;
abstract stopJobs(jobId: number | string): Observable<string> | Promise<string> | string;
}
/**
@ -301,4 +303,10 @@ export class ReplicationDefaultService extends ReplicationService {
.then(response => response.text())
.catch(error => Promise.reject(error));
}
public stopJobs(jobId: number | string): Observable<any> | Promise<any> | any {
return this.http.put(this._jobBaseUrl, JSON.stringify({'policy_id': jobId, 'status': 'stop' }), HTTP_JSON_OPTIONS).toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
}

View File

@ -65,5 +65,5 @@ export const enum ConfirmationState {
};
export const enum ConfirmationButtons {
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, REPLICATE_CANCEL
};

View File

@ -25,7 +25,7 @@ export const TAG_TEMPLATE = `
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
<clr-dg-action-bar>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!canScanNow(selectedRow)" [disabled]="!(selectedRow.length==1)" (click)="scanNow(selectedRow)">{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(canScanNow(selectedRow) && selectedRow.length==1)" (click)="scanNow(selectedRow)">{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length==1)" (click)="showDigestId(selectedRow)" >{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length">{{'REPOSITORY.DELETE' | translate}}</button>
</div>
@ -38,7 +38,7 @@ export const TAG_TEMPLATE = `
<clr-dg-column style="min-width: 130px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
<clr-dg-column style="width: 160px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
<clr-dg-column style="width: 80px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-placeholder>{{'TAG.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
<clr-dg-cell class="truncated" style="width: 160px;" [ngSwitch]="withClair">
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>

View File

@ -286,8 +286,7 @@ export class TagComponent implements OnInit {
if (tags && tags.length) {
let promiseLists: any[] = [];
tags.forEach(tag => {
this.delOperate(tag.signature, tag.name);
promiseLists.push(this.delOperate(tag.signature, tag.name));
});
Promise.all(promiseLists).then((item) => {

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.10.17",
"clarity-ui": "^0.10.17",
"core-js": "^2.4.1",
"harbor-ui": "0.6.13",
"harbor-ui": "0.6.19",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",

View File

@ -4,7 +4,7 @@
<span class="alert-text">
{{message}}
</span>
<div class="alert-actions" *ngIf="needAuth">
<div class="alert-actions" *ngIf="needAuth" style="display: inline;">
<button class="btn alert-action" (click)="signIn()">{{ 'BUTTON.LOG_IN' | translate }}</button>
</div>
</div>

View File

@ -156,7 +156,7 @@ export class MemberComponent implements OnInit, OnDestroy {
nameArr.join(','),
m,
ConfirmationTargets.PROJECT_MEMBER,
ConfirmationButtons.DELETE_CANCEL
ConfirmationButtons.SWITCH_CANCEL
);
this.OperateDialogService.openComfirmDialog(switchMessage);
}

View File

@ -1,3 +1,3 @@
<div style="margin-top: 24px;">
<hbr-replication [readonly]="true" #replicationView [projectId]="projectIdentify" [withReplicationJob]='true'></hbr-replication>
<hbr-replication [readonly]="true" #replicationView [projectId]="projectIdentify" [isSystemAdmin]="isSystemAdmin" [withReplicationJob]='true' (openCreateRule)="openCreatePage()" (openEdit)="openEditPage($event)"></hbr-replication>
</div>

View File

@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {ActivatedRoute, Router} from '@angular/router';
import { ReplicationComponent } from 'harbor-ui';
import {SessionService} from "../shared/session.service";
@Component({
selector: 'replicaton',
@ -23,12 +24,27 @@ export class ReplicationPageComponent implements OnInit, AfterViewInit {
projectIdentify: string | number;
@ViewChild("replicationView") replicationView: ReplicationComponent;
constructor(private route: ActivatedRoute) { }
constructor(private route: ActivatedRoute,
private router: Router,
private session: SessionService) { }
ngOnInit(): void {
this.projectIdentify = +this.route.snapshot.parent.params['id'];
}
public get isSystemAdmin(): boolean {
let account = this.session.getCurrentUser();
return account != null && account.has_admin_role > 0;
}
openEditPage(id: number): void {
this.router.navigate(['harbor', 'replications', id, 'rule', { projectId: this.projectIdentify}]);
}
openCreatePage(): void {
this.router.navigate(['harbor', 'replications', 'new-rule', { projectId: this.projectIdentify}] );
}
ngAfterViewInit(): void {
let isCreated: boolean = this.route.snapshot.queryParams['is_create'];
if (isCreated) {

View File

@ -14,7 +14,6 @@ import {Subject} from "rxjs/Subject";
import {ListProjectModelComponent} from "./list-project-model/list-project-model.component";
import {toPromise, isEmptyObject, compareValue} from "harbor-ui/src/utils";
const ONE_HOUR_SECONDS: number = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@ -27,15 +26,18 @@ const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestroy {
_localTime: Date = new Date();
policyId: number;
projectId: number;
targetList: Target[] = [];
isFilterHide: boolean = false;
weeklySchedule: boolean;
isScheduleOpt: boolean;
isImmediate: boolean = true;
noProjectInfo: string;
noEndpointInfo: string;
filterCount: number = 0;
selectedprojectList: Project[] = [];
triggerNames: string[] = ['immediate', 'schedule', 'manual'];
scheduleNames: string[] = ['daily', 'weekly'];
triggerNames: string[] = ['Immediate', 'Scheduled', 'Manual'];
scheduleNames: string[] = ['Daily', 'Weekly'];
weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
filterSelect: string[] = ['repository', 'tag'];
ruleNameTooltip: string = 'TOOLTIP.EMPTY';
@ -75,25 +77,33 @@ export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestro
Promise.all([this.repService.getEndpoints(), this.repService.listProjects()])
.then(res => {
if (!res[0] || !res[1]) {
this.msgHandler.error('REPLICATION.BACKINFO');
setTimeout(() => {
this.router.navigate(['/harbor/replications']);
}, 2000);
};
if (res[0] && res[1]) {
if (!res[0]) {
this.noEndpointInfo = 'NO_ENDPOINT_INFO';
}else {
this.targetList = res[0];
if (!this.policyId) {
this.setTarget([res[0][0]]);
this.setProject([res[1][0]]);
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
}
}
if (!res[1]) {
this.noProjectInfo = 'NO_PROJECT_INFO';
}else {
if (!this.policyId && !this.projectId) {
this.setProject([res[1][0]]);
}
if (!this.policyId && this.projectId) {
this.setProject( res[1].filter(rule => rule.project_id === this.projectId));
}
}
if (res[0] && res[1] && !this.policyId) {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
}
});
}
ngOnInit(): void {
this.policyId = +this.route.snapshot.params['id'];
this.projectId = +this.route.snapshot.params['projectId'];
if (this.policyId) {
this.headerTitle = 'REPLICATION.EDIT_POLICY_TITLE';
this.repService.getReplicationRule(this.policyId)
@ -406,7 +416,12 @@ export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestro
this.inProgress = false;
setTimeout(() => {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
if (this.projectId) {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}else {
this.router.navigate(['/harbor/replications']);
}
}, 2000);
}).catch((error: any) => {
@ -420,7 +435,11 @@ export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestro
this.inProgress = false;
setTimeout(() => {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
if (this.projectId) {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}else {
this.router.navigate(['/harbor/replications']);
}
}, 2000);
}).catch((error: any) => {
@ -502,6 +521,10 @@ export class ReplicationRuleComponent implements OnInit, AfterViewInit, OnDestro
backReplication(): void {
this.router.navigate(['/harbor/replications']);
}
backProjectReplication(): void {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}
getChanges(): { [key: string]: any | any[] } {
let changes: { [key: string]: any | any[] } = {};

View File

@ -16,6 +16,9 @@
h4{
color: #666;
}
.colorRed{color: red;}
.colorRed a{text-decoration: underline;color: #007CBB;}
label:first-child {
font-size: 15px;
left: -10px !important;
@ -31,4 +34,4 @@ label:first-child {
.projectInput{float: left;}
.projectInput input{width: 185px;background-color: white;}
.switchIcon{width:20px;height:20px; margin-top: 5px;margin-left: 15px;}
.switchIcon{width:20px;height:20px; margin-top: 2px;margin-left: 15px;}

View File

@ -1,10 +1,11 @@
<div>
<a class="cursor" (click)="backReplication()">< {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a>
<a class="cursor" *ngIf="!projectId" (click)="backReplication()">< {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a>
<a class="cursor" *ngIf="projectId" (click)="backProjectReplication()"><{{'SIDE_NAV.PROJECTS' | translate}} &nbsp; {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a>
<h1 class="sub-header-title">{{headerTitle | translate}}</h1>
<form [formGroup]="ruleForm" (ngSubmit)="onSubmit()" novalidate>
<section class="form-block">
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
<label class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span class="colorRed">*</span></label>
<label class="col-md-8" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='(ruleForm.controls.name.touched && ruleForm.controls.name.invalid) || isRuleNameExist'>
<input type="text" id="ruleName" required formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
@ -19,13 +20,14 @@
<!--Projects-->
<h4>{{'REPLICATION.SOURCE' | translate}}</h4>
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'PROJECT.PROJECTS' | translate}}<span style="color: red">*</span></label>
<label class="col-md-4 form-group-label-override">{{'PROJECT.PROJECTS' | translate}}<span class="colorRed">*</span></label>
<div formArrayName="projects">
<div class="projectInput" *ngFor="let project of projects.controls; let i= index" [formGroupName]="i">
<input formControlName="name" class="label" readonly value="name">
</div>
</div>
<clr-icon shape="switch" class="is-solid switchIcon" (click)="openProjectModel()"></clr-icon>
<clr-icon *ngIf="!(noProjectInfo || projectId)" shape="search" class="is-solid switchIcon" (click)="openProjectModel()"></clr-icon>
<label *ngIf="noProjectInfo" class="colorRed">{{noProjectInfo | translate}}</label>
</div>
<!--images/Filter-->
@ -53,20 +55,21 @@
<!--Targets-->
<h4>{{'REPLICATION.TARGETS' | translate}}</h4>
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'DESTINATION.ENDPOINT' | translate}} <span style="color: red">*</span></label>
<label class="col-md-4 form-group-label-override">{{'DESTINATION.ENDPOINT' | translate}} <span class="colorRed">*</span></label>
<div formArrayName="targets">
<div class="select endpointSelect" *ngFor="let target of targets.controls; let i= index" [formGroupName]="i">
<select id="ruleTarget" (change)="targetChange($event)" formControlName="id">
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}: {{target.endpoint}}</option>
</select>
</div>
<label *ngIf="noEndpointInfo" class="colorRed">{{noEndpointInfo | translate}}</label>
</div>
</div>
<!--Trigger-->
<h4>{{'REPLICATION.TRIGGER' | translate}}</h4>
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.SCHEDULE' | translate}}</label>
<label class="col-md-4 form-group-label-override">{{'REPLICATION.MODE' | translate}}</label>
<div formGroupName="trigger">
<!--on trigger-->
<div class="select floatSet">
@ -100,7 +103,6 @@
</div>
</div>
<!--Setting-->
<h4>{{'REPLICATION.SETTING' | translate}}</h4>
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.SETTING' | translate}}</label>
<div class="col-lg-7 padLeft0">

View File

@ -29,11 +29,16 @@
</ng-template>
<ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="delete()" [hidden]="isDelete">{{isSwitch? 'BUTTON.SWITCH':'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.SWITCH' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
</div>
</clr-modal>

View File

@ -45,13 +45,6 @@ export class ConfirmationDialogComponent implements OnDestroy {
return false;
}
get isSwitch(): boolean {
if (this.dialogTitle && (this.dialogTitle.includes('SWITCH') || this.dialogTitle.includes('switch'))) {
return true;
}
return false;
}
colorChange(list: BatchInfo) {
if (!list.loading && !list.errorState) {
return 'green';
@ -119,7 +112,7 @@ export class ConfirmationDialogComponent implements OnDestroy {
this.close();
}
delete(): void {
operate(): void {
if(!this.message){//Inproper condition
this.close();
return;

View File

@ -51,6 +51,7 @@ export const ListMode = {
FULL: "full"
};
export const CommonRoutes = {
SIGN_IN: "/sign-in",
EMBEDDED_SIGN_IN: "/harbor/sign-in",
@ -68,7 +69,7 @@ export const enum ConfirmationState {
NA, CONFIRMED, CANCEL
}
export const enum ConfirmationButtons {
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, SWITCH_CANCEL
}
export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' };

View File

@ -258,8 +258,6 @@ export class UserComponent implements OnInit, OnDestroy {
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.currentTerm = '';
this.msgHandler.showSuccess('USER.DELETE_SUCCESS');
this.refresh();
});
}
@ -267,7 +265,7 @@ export class UserComponent implements OnInit, OnDestroy {
delOperate(id: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return this.userService.deleteUser(id).then(() => {
this.translate.get('BATCH.DELETE_SUCCESS').subscribe(res => {
this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
}).catch(error => {

View File

@ -34,13 +34,16 @@
"NEGATIVE": "NEGATIVE",
"COPY": "COPY",
"EDIT": "EDIT",
"SWITCH": "SWITCH"
"SWITCH": "SWITCH",
"REPLICATE": "REPLICATE"
},
"BATCH": {
"DELETED_SUCCESS": "Deleted successfully",
"DELETED_FAILURE": "Deleted failed",
"SWITCH_SUCCESS": "Switch successfully",
"SWITCH_FAILURE": "Switch failed"
"SWITCH_FAILURE": "Switch failed",
"REPLICATE_SUCCESS": "Replicate successfully",
"REPLICATE_FAILURE": "Replicate failed"
},
"TOOLTIP": {
"EMAIL": "Email should be a valid email address like name@example.com.",
@ -242,8 +245,10 @@
"ENDPOINTS": "Endpoints",
"FILTER_POLICIES_PLACEHOLDER": "Filter Rules",
"FILTER_JOBS_PLACEHOLDER": "Filter Jobs",
"DELETION_TITLE": "Confirm Rule Deletion",
"DELETION_SUMMARY": "Do you want to delete rule {{param}}?",
"DELETION_TITLE": "Confirm Rules Deletion",
"DELETION_SUMMARY": "Do you want to delete rules {{param}}?",
"REPLICATION_TITLE": "Confirm Rules replication",
"REPLICATION_SUMMARY": "Do you want to replicate the Rules {{param}}?",
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
"FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints",
@ -276,6 +281,7 @@
"LAST_START_TIME": "Last Start Time",
"ACTIVATION": "Activation",
"REPLICATION_JOBS": "Replication Jobs",
"STOPJOB": "Stop Jobs",
"ALL": "All",
"PENDING": "Pending",
"RUNNING": "Running",
@ -311,16 +317,20 @@
"PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log",
"BACKINFO": "Please add project and endpoint first",
"NO_ENDPOINT_INFO": "Please go to registries and add an endpoint first",
"NO_PROJECT_INFO": "Please go to projects and add a project first",
"FILTER": "Filter",
"SCHEDULE": "Schedule",
"SETTING":"Setting",
"TRIGGER":"Trigger",
"SCHEDULE": "Scheduled",
"MANUAL": "Manual",
"IMMEDIATE": "Immediate",
"SETTING":"Options",
"TRIGGER":"Triggering Condition",
"TARGETS":"Target",
"MODE": "Mode",
"SOURCE": "Source",
"REPLICATE": "Replicate",
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
"REPLICATE_IMMEDIATE":"Replicate exiting images immediately"
"REPLICATE_IMMEDIATE":"Replicate existing images immediately"
},
"DESTINATION": {
"NEW_ENDPOINT": "New Endpoint",
@ -500,7 +510,7 @@
"ABOUT": {
"VERSION": "Version",
"BUILD": "Build",
"COPYRIGHT": "Copyright 1998-2017 VMware, Inc. All rights reserved. This product is protected by U.S. and international property laws. VMware products are covered by one or more patents listed at",
"COPYRIGHT": "Copyright 1998-2018 VMware, Inc. All rights reserved. This product is protected by U.S. and international property laws. VMware products are covered by one or more patents listed at",
"COPYRIGHT_SUFIX": ".",
"TRADEMARK": "VMware is a registered trademark or trademark of VMware, Inc. in the United States and other jurisdictions. All other marks and names mentioned herein may be trademark of their respective companies.",
"END_USER_LICENSE": "End User License Agreement",

View File

@ -34,13 +34,16 @@
"NEGATIVE": "NEGATIVO",
"COPY": "COPY",
"EDIT": "EDITAR",
"SWITCH": "SWITCH"
"SWITCH": "SWITCH",
"REPLICATE": "REPLICATE"
},
"BATCH": {
"DELETED_SUCCESS": "Deleted successfully",
"DELETED_FAILURE": "Deleted failed",
"SWITCH_SUCCESS": "Switch successfully",
"SWITCH_FAILURE": "Switch failed"
"SWITCH_FAILURE": "Switch failed",
"REPLICATE_SUCCESS": "Replicate successfully",
"REPLICATE_FAILURE": "Replicate failed"
},
"TOOLTIP": {
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
@ -245,6 +248,8 @@
"DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?",
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
"REPLICATION_TITLE": "Confirm Rules replication",
"REPLICATION_SUMMARY": "Do you want to replicate the Rules {{param}}?",
"FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints",
"DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint",
"DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?",
@ -275,6 +280,7 @@
"LAST_START_TIME": "Última Fecha de Inicio",
"ACTIVATION": "Activación",
"REPLICATION_JOBS": "Trabajos de Replicación",
"STOPJOB": "Stop Jobs",
"ALL": "Todos",
"PENDING": "Pendiente",
"RUNNING": "Ejecutando",
@ -310,15 +316,19 @@
"PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log",
"BACKINFO": "Please add project and endpoint first",
"NO_ENDPOINT_INFO": "Please go to registries and add an endpoint first",
"NO_PROJECT_INFO": "Please go to projects and add a project first",
"FILTER": "Filter",
"SCHEDULE": "Schedule",
"SETTING":"Setting",
"TRIGGER":"Trigger",
"SCHEDULE": "Scheduled",
"MANUAL": "Manual",
"IMMEDIATE": "Immediate",
"SETTING":"Options",
"TRIGGER":"Triggering Condition",
"TARGETS":"Target",
"MODE": "Mode",
"SOURCE": "Source",
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
"REPLICATE_IMMEDIATE":"Replicate exiting images immediately"
"REPLICATE_IMMEDIATE":"Replicate existing images immediately"
},
"DESTINATION": {
"NEW_ENDPOINT": "Nuevo Endpoint",
@ -499,7 +509,7 @@
"ABOUT": {
"VERSION": "Versión",
"BUILD": "Construir",
"COPYRIGHT": "Copyright 1998-2017 VMware, Inc. Todos los derechos reservados. Este producto está protegido por E.U. y las leyes de propiedad internacionales. Los productos de VMware estan cubiertos por una o más patentes listadas en",
"COPYRIGHT": "Copyright 1998-2018 VMware, Inc. Todos los derechos reservados. Este producto está protegido por E.U. y las leyes de propiedad internacionales. Los productos de VMware estan cubiertos por una o más patentes listadas en",
"COPYRIGHT_SUFIX": ".",
"TRADEMARK": "VMware es una marca registrada o marca de VMware, Inc. en los Estados Unidos y otras jurisdicciones. Todas las demás marcas y nombres mencionados son marcas de sus respectivas compañías.",
"END_USER_LICENSE": "Contrato de Usuario Final (EULA)",

View File

@ -34,13 +34,16 @@
"NEGATIVE": "否",
"COPY": "拷贝",
"EDIT": "编辑",
"SWITCH": "切换"
"SWITCH": "切换",
"REPLICATE": "复制"
},
"BATCH": {
"DELETED_SUCCESS": "删除成功",
"DELETED_FAILURE": "删除失败",
"SWITCH_SUCCESS": "切换成功",
"SWITCH_FAILURE": "切换失败"
"SWITCH_FAILURE": "切换失败",
"REPLICATE_SUCCESS": "复成功",
"REPLICATE_FAILURE": "复制失败"
},
"TOOLTIP": {
"EMAIL": "请使用正确的邮箱地址比如name@example.com。",
@ -244,6 +247,8 @@
"DELETION_TITLE": "删除规则确认",
"DELETION_SUMMARY": "确认删除规则 {{param}}?",
"DELETION_TITLE_FAILURE": "规则确认删除失败",
"REPLICATION_TITLE": "复制规则确认",
"REPLICATION_SUMMARY": "确认复制规则 {{param}}?",
"DELETION_SUMMARY_FAILURE": "{{param}} 有 pending/running/retrying 状态,不能删除",
"FILTER_TARGETS_PLACEHOLDER": "过滤目标",
"DELETION_TITLE_TARGET": "删除目标确认",
@ -275,6 +280,7 @@
"LAST_START_TIME": "上次起始时间",
"ACTIVATION": "活动状态",
"REPLICATION_JOBS": "复制任务",
"STOPJOB": "停止任务",
"ALL": "全部",
"PENDING": "挂起",
"RUNNING": "运行中",
@ -310,12 +316,16 @@
"PLACEHOLDER": "未发现任何复制规则!",
"JOB_PLACEHOLDER": "未发现任何复制任务!",
"JOB_LOG_VIEWER": "查看复制任务日志",
"BACKINFO": "请先添加项目名称和目标",
"NO_ENDPOINT_INFO": "请先添加目标",
"NO_PROJECT_INFO": "请先添加项目",
"FILTER": "过滤",
"SCHEDULE": "日程",
"SCHEDULE": "定时",
"MANUAL": "手动",
"IMMEDIATE": "即刻",
"SETTING":"设置",
"TRIGGER":"触发器",
"TRIGGER":"触发条件",
"TARGETS":"目标",
"MODE": "模式",
"SOURCE": "资源",
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。",
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。"
@ -498,7 +508,7 @@
"ABOUT": {
"VERSION": "版本",
"BUILD": "构建",
"COPYRIGHT": "版权所有 © 1998-2017 VMware, Inc. 保留所有权利。此产品受美国及其他国家/地区的版权和知识产权以及国际条约保护。VMware产品受",
"COPYRIGHT": "版权所有 © 1998-2018 VMware, Inc. 保留所有权利。此产品受美国及其他国家/地区的版权和知识产权以及国际条约保护。VMware产品受",
"COPYRIGHT_SUFIX": "上列出的一项或多项专利保护。",
"TRADEMARK": "VMware徽标及设计都是VMware, Inc.在美国和/或其他法律辖区的注册商标或者商标。此处提到的其他所有商标和名称分别是其各自公司的商标。",
"END_USER_LICENSE": "终端用户许可协议",

View File

@ -325,8 +325,7 @@ Test Case - Delete Multi User
Filter Object delete
Multi-delete Object deletea deleteb deletec
#assert delete
#Delete Success comment temp wait for fixing
Click Element //clr-modal//button[contains(.,'CLOSE')]
Delete Success
Sleep 1
#filter object delete
Page Should Not Contain deletea