Merge pull request #7227 from zhoumeina/replication_ng

add filter logic
This commit is contained in:
Wenkai Yin 2019-03-28 08:43:43 +08:00 committed by GitHub
commit c0faa9d4aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 43 additions and 303 deletions

View File

@ -2,15 +2,6 @@
<h3 class="modal-title">{{headerTitle | translate}}</h3>
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
<div class="modal-body modal-body-height">
<clr-alert [hidden]='!deletedLabelCount' [clrAlertType]="'alert-warning'" [clrAlertSizeSmall]="true" [clrAlertClosable]="false"
[(clrAlertClosed)]="alertClosed">
<div class="alert-item">
<span class="alert-text">{{deletedLabelInfo}}</span>
<div class="alert-actions">
<a class="alert-action" (click)=" alertClosed = true">{{'REPLICATION.ACKNOWLEDGE' | translate}}</a>
</div>
</div>
</clr-alert>
<form [formGroup]="ruleForm" novalidate>
<section class="form-block">
<div class="form-group form-group-override">
@ -31,7 +22,7 @@
<div class="form-group form-group-override">
<label class="form-group-label-override">{{'REPLICATION.REPLI_MODE' | translate}}</label>
<div class="radio" style="display:inherit;">
<input type="radio" id="push_base" name="replicationMode" [value]=true [(ngModel)]="isPushMode" (change)="modeChange()" [ngModelOptions]="{standalone: true}">
<input type="radio" id="push_base" name="replicationMode" [value]=true [(ngModel)]="isPushMode" (change)="modeChange()" [ngModelOptions]="{standalone: true}">
<label for="push_base">Push-based</label>
<input type="radio" id="pull_base" name="replicationMode" [value]=false [(ngModel)]="isPushMode" [ngModelOptions]="{standalone: true}">
<label for="pull_base">Pull-based</label>
@ -55,16 +46,16 @@
<label class="form-group-label-override required">{{'REPLICATION.SOURCE_NAMESPACES' | translate}}</label>
<div formArrayName="src_namespaces">
<div class="width-315" *ngFor="let src_namespace of src_namespaces.controls; let i=index">
<div class="select endpointSelect pull-left">
<div class="endpointSelect pull-left">
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='(src_namespace.touched && src_namespace.invalid)'>
<input type="text" [formControlName]="i" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth" required maxlength="255">
<input type="text" [formControlName]="i" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth" required maxlength="255">
<span class="tooltip-content">{{'REPLICATION.NAMESPACE_TOOLTIP' | translate}}</span>
</label>
</div>
<clr-icon shape="times-circle" class="is-solid" (click)="deleteNamespace(i)"></clr-icon>
</div>
</div>
<clr-icon id="addSourceNamespace" shape="plus-circle" class="is-solid mr-t-15" (click)="addNewNamespace()"></clr-icon>
<clr-icon id="addSourceNamespace" shape="plus-circle" class="is-solid mr-t-10" (click)="addNewNamespace()"></clr-icon>
</div>
<!--images/Filter-->
<div class="form-group form-group-override">
@ -72,24 +63,20 @@
<div formArrayName="filters">
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index">
<div [formGroupName]="i">
<div class="select floatSetPar">
<select formControlName="kind" #selectedValue (change)="filterChange($event, selectedValue.value)" id="{{i}}" name="{{filterListData[i]?.name}}">
<option *ngFor="let opt of filterListData[i]?.options;" value="{{opt}}">{{opt}}</option>
</select>
<div class="floatSetPar">
<label>{{supportedFilters[i].type}}:</label>
</div>
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='(filter.value.dirty || filter.value.touched) && filter.value.invalid'>
<input type="text" #filterValue required size="14" formControlName="value" [attr.disabled]="(filterListData[i]?.name=='label') ?'' : null">
<label *ngIf="supportedFilters[i]?.style==='input'" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='(filter.value.dirty || filter.value.touched) && filter.value.invalid'>
<input type="text" #filterValue required size="14" formControlName="value">
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
</label>
<div class="arrowSet" [hidden]="!(filterListData[i]?.name=='label')" (click)="openLabelList(selectedValue.value, i, $event)">
<clr-icon shape="angle"></clr-icon>
</div>
<clr-icon shape="warning-standard" class="is-solid is-warning warning-icon" size="14" [hidden]="!deletedLabelCount || !(filterListData[i]?.name=='label')"></clr-icon>
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
<div *ngIf="!withAdmiral">
<hbr-filter-label [projectId]="projectId" [selectedLabelInfo]="filterLabelInfo" [isOpen]="filterListData[i].isOpen" (selectedLabels)="selectedLabelList($event, i)"
(closePanelEvent)="filterListData[i].isOpen = false"></hbr-filter-label>
<div class="select inline-block" *ngIf="supportedFilters[i]?.style==='radio'">
<select formControlName="value" #selectedValue id="{{i}}" name="{{supportedFilters[i]?.type}}">
<option *ngFor="let value of supportedFilters[i]?.values;" value="{{value}}">{{value}}</option>
</select>
</div>
<clr-icon *ngIf="i=== filters.length-1" shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
</div>
</div>
</div>
@ -113,7 +100,7 @@
<div class="form-group form-group-override">
<label class="form-group-label-override">{{'REPLICATION.DEST_NAMESPACE' | translate}}</label>
<div class="form-select">
<div class="select endpointSelect pull-left">
<div class="endpointSelect pull-left">
<input formControlName="dest_namespace" type="text" id="dest_namespace" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth"
maxlength="255">
</div>
@ -127,7 +114,7 @@
<!--on trigger-->
<div class="select floatSetPar">
<select id="ruleTrigger" formControlName="kind" (change)="selectTrigger($event)">
<option *ngFor="let trigger of supportedTriggers" [value]="trigger">{{trigger }}</option>
<option *ngFor="let trigger of supportedTriggers" [value]="trigger">{{trigger }}</option>
</select>
</div>
<!--on push-->

View File

@ -1,5 +1,5 @@
.select {
width: 186px;
width: 190px;
}
.select .optionMore {
@ -40,7 +40,7 @@ h4 {
.endpointSelect {
width: 270px;
margin-right: 20px;
margin-right: 10px;
}
.filterSelect {
@ -49,11 +49,11 @@ h4 {
}
.filterSelect clr-icon {
margin-left: 15px;
margin-left: 10px;
}
.filterSelect label {
width: 136px;
width: 190px;
}
.filterSelect label input {
@ -70,7 +70,7 @@ h4 {
.floatSetPar {
display: inline-block;
width: 120px;
width: 70px;
margin-right: 10px;
}
@ -229,6 +229,10 @@ clr-modal {
width:315px;
}
.mr-t-15 {
margin-top: 15px;
.mr-t-10 {
margin-top: 10px;
}
.inline-block {
display: inline-block;
}

View File

@ -33,10 +33,7 @@ import {
EndpointService,
EndpointDefaultService
} from "../service/endpoint.service";
import {
ProjectDefaultService,
ProjectService
} from "../service/project.service";
import { OperationService } from "../operation/operation.service";
import {FilterLabelComponent} from "./filter-label.component";
import {LabelService} from "../service/label.service";
@ -233,7 +230,6 @@ describe("CreateEditRuleComponent (inline template)", () => {
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService },
{ provide: OperationService },
{ provide: LabelService }
@ -275,7 +271,6 @@ describe("CreateEditRuleComponent (inline template)", () => {
it("Should open creation modal and load endpoints", async(() => {
fixture.detectChanges();
compCreate.initAdapter("harbor");
compCreate.openCreateEditRule();
fixture.whenStable().then(() => {
fixture.detectChanges();
@ -291,7 +286,6 @@ describe("CreateEditRuleComponent (inline template)", () => {
it("Should open modal to edit replication rule", async(() => {
fixture.detectChanges();
compCreate.initAdapter("harbor");
compCreate.openCreateEditRule(mockRule.id);
fixture.whenStable().then(() => {
fixture.detectChanges();

View File

@ -21,7 +21,7 @@ import {
EventEmitter,
Output
} from "@angular/core";
import { Filter, ReplicationRule, Endpoint, Label, Adapter } from "../service/interface";
import { Filter, ReplicationRule, Endpoint, Adapter } from "../service/interface";
import { Subject, Subscription } from "rxjs";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { FormArray, FormBuilder, FormGroup, Validators, FormControl } from "@angular/forms";
@ -31,9 +31,6 @@ import { ReplicationService } from "../service/replication.service";
import { ErrorHandler } from "../error-handler/error-handler";
import { TranslateService } from "@ngx-translate/core";
import { EndpointService } from "../service/endpoint.service";
import { ProjectService } from "../service/project.service";
import { Project } from "../project-policy-config/project";
import { LabelState } from "../tag/tag.component";
const ONE_HOUR_SECONDS = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@ -47,14 +44,10 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
_localTime: Date = new Date();
sourceList: Endpoint[] = [];
targetList: Endpoint[] = [];
projectList: Project[] = [];
selectedProjectList: Project[] = [];
isFilterHide = false;
weeklySchedule: boolean;
noProjectInfo = "";
noEndpointInfo = "";
isPushMode = true;
noSelectedProject = true;
noSelectedEndpoint = true;
filterCount = 0;
alertClosed = false;
@ -63,7 +56,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
SCHEDULED: "Scheduled",
EVENT_BASED: "EventBased"
};
filterSelect: string[] = ["type", "repository", "tag", "label"];
ruleNameTooltip = "REPLICATION.NAME_TOOLTIP";
headerTitle = "REPLICATION.ADD_POLICY";
@ -75,20 +68,12 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
nameChecker: Subject<string> = new Subject<string>();
firstClick = 0;
policyId: number;
labelInputVal = '';
filterLabelInfo: Label[] = []; // store filter selected labels` id
deletedLabelCount = 0;
deletedLabelInfo: string;
confirmSub: Subscription;
ruleForm: FormGroup;
formArrayLabel: FormArray;
copyUpdateForm: ReplicationRule;
cronString: string;
supportedTriggers: string[];
supportedFilters: Filter[];
@Input() projectId: number;
@Input() projectName: string;
@Input() withAdmiral: boolean;
@Output() goToRegistry = new EventEmitter<any>();
@ -100,27 +85,17 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
private repService: ReplicationService,
private endpointService: EndpointService,
private errorHandler: ErrorHandler,
private proService: ProjectService,
private translateService: TranslateService,
private ref: ChangeDetectorRef
) {
this.createForm();
}
baseFilterData(name: string, option: string[], state: boolean) {
return {
name: name,
options: option,
state: state,
isValid: true,
isOpen: false // label list
};
}
initAdapter(type: string): void {
this.repService.getReplicationAdapter(type).subscribe(adapter => {
this.supportedFilters = adapter.supported_resource_filters;
this.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("kind").setValue(this.supportedTriggers[0]);
});
}
@ -131,7 +106,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}, error => {
this.errorHandler.error(error);
});
this.initAdapter("harbor");
this.nameChecker
.pipe(debounceTime(300))
.pipe(distinctUntilChanged())
@ -195,7 +169,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
createForm() {
this.formArrayLabel = this.fb.array([]);
this.ruleForm = this.fb.group({
name: ["", Validators.required],
description: "",
@ -231,7 +204,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
name: "",
description: "",
trigger: {
kind: this.supportedTriggers[0],
kind: '',
schedule_param: {
cron: ""
}
@ -239,6 +212,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
deletion: false
});
this.setFilter([]);
this.initAdapter("harbor");
this.copyUpdateForm = clone(this.ruleForm.value);
}
@ -252,11 +226,9 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
deletion: rule.deletion
});
this.noSelectedProject = false;
this.noSelectedEndpoint = false;
if (rule.filters) {
this.reOrganizeLabel(rule.filters);
this.setFilter(rule.filters);
this.updateFilter(rule.filters);
}
@ -266,45 +238,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
setTimeout(() => clearInterval(hnd), 2000);
}
// reorganize filter structure
reOrganizeLabel(filterLabels: any[]): void {
let count = 0;
if (filterLabels.length) {
this.filterLabelInfo = [];
let delLabel = '';
filterLabels.forEach((data: any) => {
if (data.kind === this.filterSelect[3]) {
if (!data.value.deleted) {
count++;
this.filterLabelInfo.push(data.value);
} else {
this.deletedLabelCount++;
delLabel += data.value.name + ',';
}
}
});
this.translateService.get('REPLICATION.DELETED_LABEL_INFO', {
param: delLabel
}).subscribe((res: string) => {
this.deletedLabelInfo = res;
this.alertClosed = false;
});
// delete api return label info, replace with label count
if (delLabel || count) {
let len = filterLabels.length;
for (let i = 0; i < len; i++) {
let lab = filterLabels.find(data => data.kind === this.filterSelect[3]);
if (lab) { filterLabels.splice(filterLabels.indexOf(lab), 1); }
}
filterLabels.push({ kind: 'label', value: count + ' labels' });
this.labelInputVal = count.toString();
}
}
}
get filters(): FormArray {
return this.ruleForm.get("filters") as FormArray;
}
@ -316,63 +249,11 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
initFilter(name: string) {
return this.fb.group({
kind: name,
type: name,
value: ''
});
}
filterChange($event: any, selectedValue: string) {
if ($event && $event.target["value"]) {
let id: number = $event.target.id;
let name: string = $event.target.name;
let value: string = $event.target["value"];
const controlArray = <FormArray>this.ruleForm.get('filters');
this.filterListData.forEach((data, index) => {
if (index === +id) {
data.name = $event.target.name = value;
} else {
data.options.splice(data.options.indexOf(value), 1);
}
if (data.options.indexOf(name) === -1) {
data.options.push(name);
}
// if before select, $event is label
if (!this.withAdmiral && name === this.filterSelect[3] && data.name === value) {
this.labelInputVal = controlArray.controls[index].get('value').value.split(' ')[0];
data.isOpen = false;
controlArray.controls[index].get('value').setValue('');
}
// if before select, $event is not label
if (!this.withAdmiral && data.name === this.filterSelect[2]) {
if (this.labelInputVal) {
controlArray.controls[index].get('value').setValue(this.labelInputVal + ' labels');
} else {
controlArray.controls[index].get('value').setValue('');
}
// this.labelInputVal = '';
data.isOpen = false;
}
});
}
}
// when input value is label, then open label panel
openLabelList(labelTag: string, indexId: number, $event: any) {
if (!this.withAdmiral && labelTag === this.filterSelect[3]) {
this.filterListData.forEach((data, index) => {
if (index === indexId) {
data.isOpen = true;
} else {
data.isOpen = false;
}
});
}
}
targetChange($event: any) {
if ($event && $event.target) {
if ($event.target["value"] === "-1") {
@ -392,10 +273,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
}
leaveInput() {
this.selectedProjectList = [];
}
addNewNamespace(): void {
this.src_namespaces.push(new FormControl());
}
@ -403,64 +280,22 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
deleteNamespace(index: number): void {
this.src_namespaces.removeAt(index);
}
getCurrentIndex(): number {
return this.filters.length;
}
addNewFilter(): void {
const controlArray = <FormArray>this.ruleForm.get('filters');
if (this.filterCount === 0) {
this.filterListData.push(
this.baseFilterData(
this.filterSelect[0],
this.filterSelect.slice(),
true,
)
);
controlArray.push(this.initFilter(this.filterSelect[0]));
} else {
let nameArr: string[] = this.filterSelect.slice();
this.filterListData.forEach(data => {
nameArr.splice(nameArr.indexOf(data.name), 1);
});
// when add a new filter,the filterListData should change the options
this.filterListData.filter(data => {
data.options.splice(data.options.indexOf(nameArr[0]), 1);
});
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
controlArray.push(this.initFilter(nameArr[0]));
}
this.filterCount += 1;
if (this.filterCount >= this.filterSelect.length) {
let index = this.getCurrentIndex();
this.filters.push(this.initFilter(this.supportedFilters[index].type));
if (index + 1 >= this.supportedFilters.length) {
this.isFilterHide = true;
}
if (controlArray.controls[this.filterCount - 1].get('kind').value === this.filterSelect[3] && this.labelInputVal) {
controlArray.controls[this.filterCount - 1].get('value').setValue(this.labelInputVal + ' labels');
}
}
// delete a filter
deleteFilter(i: number): void {
if (i >= 0) {
let delFilter = this.filterListData.splice(i, 1)[0];
if (this.filterCount === this.filterSelect.length) {
this.isFilterHide = false;
}
this.filterCount -= 1;
if (this.filterListData.length) {
let optionVal = delFilter.name;
this.filterListData.filter(data => {
if (data.options.indexOf(optionVal) === -1) {
data.options.push(optionVal);
}
});
}
const control = <FormArray>this.ruleForm.get('filters');
if (control.controls[i].get('kind').value === this.filterSelect[2]) {
this.filterLabelInfo = [];
this.labelInputVal = "";
}
control.removeAt(i);
this.setFilter(control.value);
}
this.filters.removeAt(i);
this.isFilterHide = false;
}
// Replication Schedule select value exchange
@ -478,57 +313,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
updateFilter(filters: any) {
let opt: string[] = this.filterSelect.slice();
filters.forEach((filter: any) => {
opt.splice(opt.indexOf(filter.kind), 1);
});
filters.forEach((filter: any) => {
let option: string[] = opt.slice();
option.unshift(filter.kind);
this.filterListData.push(this.baseFilterData(filter.kind, option, true));
});
this.filterCount = filters.length;
if (filters.length === this.filterSelect.length) {
this.isFilterHide = true;
}
}
selectedLabelList(selectedLabels: LabelState[], indexId: number) {
// set input value of filter label
const controlArray = <FormArray>this.ruleForm.get('filters');
this.filterListData.forEach((data, index) => {
if (data.name === this.filterSelect[2]) {
let labelsLength = selectedLabels.filter(lab => lab.iconsShow === true).length;
if (labelsLength > 0) {
controlArray.controls[index].get('value').setValue(labelsLength + ' labels');
this.labelInputVal = labelsLength.toString();
} else {
controlArray.controls[index].get('value').setValue('');
}
}
});
// store filter label info
this.filterLabelInfo = [];
selectedLabels.forEach(data => {
if (data.iconsShow === true) {
this.filterLabelInfo.push(data.label);
}
});
}
setFilterLabelVal(filters: any[]) {
let labels: any = filters.find(data => data.kind === this.filterSelect[2]);
if (labels) {
filters.splice(filters.indexOf(labels), 1);
let info: any[] = [];
this.filterLabelInfo.forEach(data => {
info.push({ kind: 'label', value: data.id });
});
filters.push.apply(filters, info);
}
}
public hasFormChange(): boolean {
@ -544,8 +328,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} else {
copyRuleForm.dest_registry_id = null;
}
// rewrite key name of label when filer contain labels.
if (copyRuleForm.filters) { this.setFilterLabelVal(copyRuleForm.filters); }
if (this.policyId < 0) {
this.repService
@ -581,30 +363,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
openCreateEditRule(ruleId?: number | string): void {
this.initForm();
this.inlineAlert.close();
this.selectedProjectList = [];
this.filterCount = 0;
this.isFilterHide = false;
this.filterListData = [];
this.firstClick = 0;
this.noSelectedProject = true;
this.noSelectedEndpoint = true;
this.isRuleNameValid = true;
this.deletedLabelCount = 0;
this.weeklySchedule = false;
this.policyId = -1;
this.createEditRuleOpened = true;
this.filterLabelInfo = [];
this.labelInputVal = '';
this.noProjectInfo = "";
this.noEndpointInfo = "";
if (this.targetList.length === 0) {
this.noEndpointInfo = "REPLICATION.NO_ENDPOINT_INFO";
}
if (this.projectList.length === 0 && !this.projectName) {
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
}
if (ruleId) {
this.policyId = +ruleId;
@ -657,18 +429,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
initValueCopy[key] = initValue[key];
}
if (formValue.filters && formValue.filters.length > 0) {
formValue.filters.forEach((data, index) => {
if (data.kind === this.filterSelect[2]) {
formValue.filters.splice(index, 1);
}
});
// rewrite filter label
this.filterLabelInfo.forEach(data => {
formValue.filters.push({ kind: "label", pattern: "", value: data });
});
}
if (!compareValue(formValue, initValueCopy)) {
return true;
}

View File

@ -80,7 +80,7 @@
</clr-datagrid>
</div>
</div>
<hbr-create-edit-rule *ngIf="isSystemAdmin" [withAdmiral]="withAdmiral" [projectId]="projectId" [projectName]="projectName"
<hbr-create-edit-rule *ngIf="isSystemAdmin" [withAdmiral]="withAdmiral"
(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>

View File

@ -445,7 +445,6 @@
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
"ACKNOWLEDGE": "Acknowledge",
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
"REPLI_MODE": "Replication mode",

View File

@ -446,7 +446,6 @@
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
"ACKNOWLEDGE": "Acknowledge",
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
"REPLI_MODE": "Replication mode",

View File

@ -427,7 +427,6 @@
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
"ACKNOWLEDGE": "Acknowledge",
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
"REPLI_MODE": "Replication mode",

View File

@ -445,7 +445,6 @@
"REPLICATE_IMMEDIATE":"Replicar imagens existentes imediatamente",
"NEW": "Novo",
"NAME_TOOLTIP": "nome da regra de replicação deve conter ao menos 2 caracteres sendo caracteres minusculos, números e ._- e devem iniciar com letras e números.",
"DELETED_LABEL_INFO": "Removidas as label(s) '{{param}}' referenciadas no filtro, clique 'SALVAR' para atualizar o filtro e habilitar essa regra.",
"ACKNOWLEDGE": "Reconhecer",
"RULE_DISABLED": "Essa regra foi desabilitada pois uma label usada no seu filtro foi removida. \n Edite a regra e atualize seu filtro para habilitá-la.",
"REPLI_MODE": "Replication mode",

View File

@ -446,7 +446,6 @@
"REPLICATE_IMMEDIATE": "立即复制现有的镜像。",
"NEW": "新增",
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
"ACKNOWLEDGE": "确认",
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。",
"REPLI_MODE": "复制模式",