mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Merge pull request #5157 from pengpengshui/repLabelNew
Add replication rule label filter function #4861
This commit is contained in:
commit
1fae22add1
@ -2,6 +2,14 @@
|
|||||||
<h3 class="modal-title">{{headerTitle | translate}}</h3>
|
<h3 class="modal-title">{{headerTitle | translate}}</h3>
|
||||||
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
|
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
|
||||||
<div class="modal-body" style="max-height: 85vh;">
|
<div class="modal-body" style="max-height: 85vh;">
|
||||||
|
<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>
|
<form [formGroup]="ruleForm" novalidate>
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
@ -39,23 +47,28 @@
|
|||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
<label class="form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
|
<label class="form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
|
||||||
<div formArrayName="filters">
|
<div formArrayName="filters">
|
||||||
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index" [formGroupName]="i">
|
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index">
|
||||||
<div>
|
<div [formGroupName]="i">
|
||||||
<div class="select floatSetPar">
|
<div class="select floatSetPar">
|
||||||
<select formControlName="kind" (change)="filterChange($event)" id="{{i}}" name="{{filterListData[i]?.name}}">
|
<select formControlName="kind" #selectedValue (change)="filterChange($event, selectedValue.value)" id="{{i}}" name="{{filterListData[i]?.name}}">
|
||||||
<option *ngFor="let filter of filterListData[i]?.options;" value="{{filter}}">{{filter}}</option>
|
<option *ngFor="let opt of filterListData[i]?.options;" value="{{opt}}">{{opt}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
||||||
[class.invalid]='ruleForm.controls.filters.controls[i].controls.pattern.touched && ruleForm.controls.filters.controls[i].controls.pattern.invalid'>
|
[class.invalid]='(ruleForm.controls.filters.controls[i].controls.value.dirty || ruleForm.controls.filters.controls[i].controls.value.touched) && ruleForm.controls.filters.controls[i].controls.value.invalid'>
|
||||||
<input type="text" #filterValue required size="14" formControlName="pattern">
|
<input type="text" #filterValue required size="14" formControlName="value" [attr.disabled]="(filterListData[i]?.name=='label') ?'' : null">
|
||||||
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
|
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
|
||||||
</label>
|
</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" size="14" style="margin-left: -15px;" [hidden]="!deletedLabelCount || !(filterListData[i]?.name=='label')"></clr-icon>
|
||||||
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
|
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
|
||||||
|
<div *ngIf="!withAdmiral"><hbr-filter-label [projectId]="ruleForm.controls.projects.controls[0]?.value.project_id" [selectedLabelInfo]="filterLabelInfo" [isOpen]="filterListData[i].isOpen" (selectedLabels)="selectedLabelList($event, i)" (closePanelEvent)="filterListData[i].isOpen = false"></hbr-filter-label></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<clr-icon shape="plus-circle" class="is-solid" [hidden]="isFilterHide" (click)="addNewFilter()" style="margin-top: 11px;"></clr-icon>
|
<clr-icon shape="plus-circle" class="is-solid" [hidden]="isFilterHide" (click)="addNewFilter()" style="margin-top: 11px;"></clr-icon>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!--Targets-->
|
<!--Targets-->
|
||||||
<div class="form-group form-group-override">
|
<div class="form-group form-group-override">
|
||||||
|
@ -44,6 +44,7 @@ h4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filterSelect {
|
.filterSelect {
|
||||||
|
position: relative;
|
||||||
width: 315px;
|
width: 315px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +157,29 @@ h4 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arrowSet{
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 50px;
|
||||||
|
top: 8px;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
clr-modal {
|
clr-modal {
|
||||||
::ng-deep div.modal-dialog {
|
::ng-deep div.modal-dialog {
|
||||||
width: 25rem;
|
width: 25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deletedDiv {
|
||||||
|
text-align: left;
|
||||||
|
line-height: 14px;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.deletedDiv a{
|
||||||
|
margin-left: 10px;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,9 @@ import {
|
|||||||
} from "../service/project.service";
|
} from "../service/project.service";
|
||||||
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
|
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
|
||||||
import { OperationService } from "../operation/operation.service";
|
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";
|
||||||
|
|
||||||
describe("CreateEditRuleComponent (inline template)", () => {
|
describe("CreateEditRuleComponent (inline template)", () => {
|
||||||
let mockRules: ReplicationRule[] = [
|
let mockRules: ReplicationRule[] = [
|
||||||
@ -239,7 +242,9 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
|||||||
DatePickerComponent,
|
DatePickerComponent,
|
||||||
FilterComponent,
|
FilterComponent,
|
||||||
InlineAlertComponent,
|
InlineAlertComponent,
|
||||||
JobLogViewerComponent
|
JobLogViewerComponent,
|
||||||
|
FilterLabelComponent,
|
||||||
|
LabelPieceComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
@ -248,7 +253,8 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
|||||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||||
{ provide: JobLogService, useClass: JobLogDefaultService },
|
{ provide: JobLogService, useClass: JobLogDefaultService },
|
||||||
{ provide: OperationService }
|
{ provide: OperationService },
|
||||||
|
{ provide: LabelService }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -21,11 +21,11 @@ import {
|
|||||||
EventEmitter,
|
EventEmitter,
|
||||||
Output
|
Output
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { Filter, ReplicationRule, Endpoint } from "../service/interface";
|
import {Filter, ReplicationRule, Endpoint, Label} from "../service/interface";
|
||||||
import { Subject } from "rxjs/Subject";
|
import { Subject } from "rxjs/Subject";
|
||||||
import { Subscription } from "rxjs/Subscription";
|
import { Subscription } from "rxjs/Subscription";
|
||||||
import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms";
|
import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||||
import { compareValue, isEmptyObject, toPromise } from "../utils";
|
import {clone, compareValue, isEmptyObject, toPromise} from "../utils";
|
||||||
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
|
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
|
||||||
import { ReplicationService } from "../service/replication.service";
|
import { ReplicationService } from "../service/replication.service";
|
||||||
import { ErrorHandler } from "../error-handler/error-handler";
|
import { ErrorHandler } from "../error-handler/error-handler";
|
||||||
@ -33,6 +33,7 @@ import { TranslateService } from "@ngx-translate/core";
|
|||||||
import { EndpointService } from "../service/endpoint.service";
|
import { EndpointService } from "../service/endpoint.service";
|
||||||
import { ProjectService } from "../service/project.service";
|
import { ProjectService } from "../service/project.service";
|
||||||
import { Project } from "../project-policy-config/project";
|
import { Project } from "../project-policy-config/project";
|
||||||
|
import {LabelState} from "../tag/tag.component";
|
||||||
|
|
||||||
const ONE_HOUR_SECONDS = 3600;
|
const ONE_HOUR_SECONDS = 3600;
|
||||||
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||||
@ -56,6 +57,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
noSelectedProject = true;
|
noSelectedProject = true;
|
||||||
noSelectedEndpoint = true;
|
noSelectedEndpoint = true;
|
||||||
filterCount = 0;
|
filterCount = 0;
|
||||||
|
alertClosed = false;
|
||||||
triggerNames: string[] = ["Manual", "Immediate", "Scheduled"];
|
triggerNames: string[] = ["Manual", "Immediate", "Scheduled"];
|
||||||
scheduleNames: string[] = ["Daily", "Weekly"];
|
scheduleNames: string[] = ["Daily", "Weekly"];
|
||||||
weekly: string[] = [
|
weekly: string[] = [
|
||||||
@ -67,7 +69,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
"Saturday",
|
"Saturday",
|
||||||
"Sunday"
|
"Sunday"
|
||||||
];
|
];
|
||||||
filterSelect: string[] = ["repository", "tag"];
|
filterSelect: string[] = ["repository", "tag", "label"];
|
||||||
ruleNameTooltip = "REPLICATION.NAME_TOOLTIP";
|
ruleNameTooltip = "REPLICATION.NAME_TOOLTIP";
|
||||||
headerTitle = "REPLICATION.ADD_POLICY";
|
headerTitle = "REPLICATION.ADD_POLICY";
|
||||||
|
|
||||||
@ -80,13 +82,19 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
proNameChecker: Subject<string> = new Subject<string>();
|
proNameChecker: Subject<string> = new Subject<string>();
|
||||||
firstClick = 0;
|
firstClick = 0;
|
||||||
policyId: number;
|
policyId: number;
|
||||||
|
labelInputVal = '';
|
||||||
|
filterLabelInfo: Label[] = []; // store filter selected labels` id
|
||||||
|
deletedLabelCount = 0;
|
||||||
|
deletedLabelInfo: string;
|
||||||
|
|
||||||
confirmSub: Subscription;
|
confirmSub: Subscription;
|
||||||
ruleForm: FormGroup;
|
ruleForm: FormGroup;
|
||||||
|
formArrayLabel: FormArray;
|
||||||
copyUpdateForm: ReplicationRule;
|
copyUpdateForm: ReplicationRule;
|
||||||
|
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Input() projectName: string;
|
@Input() projectName: string;
|
||||||
|
@Input() withAdmiral: boolean;
|
||||||
|
|
||||||
@Output() goToRegistry = new EventEmitter<any>();
|
@Output() goToRegistry = new EventEmitter<any>();
|
||||||
@Output() reload = new EventEmitter<boolean>();
|
@Output() reload = new EventEmitter<boolean>();
|
||||||
@ -123,11 +131,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
name: name,
|
name: name,
|
||||||
options: option,
|
options: option,
|
||||||
state: state,
|
state: state,
|
||||||
isValid: true
|
isValid: true,
|
||||||
|
isOpen: false // label list
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.withAdmiral) {
|
||||||
|
this.filterSelect = ["repository", "tag"];
|
||||||
|
}
|
||||||
|
|
||||||
toPromise<Endpoint[]>(this.endpointService.getEndpoints())
|
toPromise<Endpoint[]>(this.endpointService.getEndpoints())
|
||||||
.then(targets => {
|
.then(targets => {
|
||||||
this.targetList = targets || [];
|
this.targetList = targets || [];
|
||||||
@ -173,7 +186,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
this.proNameChecker
|
this.proNameChecker
|
||||||
.debounceTime(500)
|
.debounceTime(500)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.subscribe((name: string) => {
|
.subscribe((resp: string) => {
|
||||||
|
let name = this.ruleForm.controls["projects"].value[0].name;
|
||||||
this.noProjectInfo = "";
|
this.noProjectInfo = "";
|
||||||
this.selectedProjectList = [];
|
this.selectedProjectList = [];
|
||||||
toPromise<Project[]>(this.proService.listProjects(name, undefined))
|
toPromise<Project[]>(this.proService.listProjects(name, undefined))
|
||||||
@ -225,6 +239,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createForm() {
|
createForm() {
|
||||||
|
this.formArrayLabel = this.fb.array([]);
|
||||||
|
|
||||||
this.ruleForm = this.fb.group({
|
this.ruleForm = this.fb.group({
|
||||||
name: ["", Validators.required],
|
name: ["", Validators.required],
|
||||||
description: "",
|
description: "",
|
||||||
@ -263,7 +279,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
this.setTarget([this.emptyEndpoint]);
|
this.setTarget([this.emptyEndpoint]);
|
||||||
this.setFilter([]);
|
this.setFilter([]);
|
||||||
|
|
||||||
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
|
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateForm(rule: ReplicationRule): void {
|
updateForm(rule: ReplicationRule): void {
|
||||||
@ -281,6 +297,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
this.noSelectedEndpoint = false;
|
this.noSelectedEndpoint = false;
|
||||||
|
|
||||||
if (rule.filters) {
|
if (rule.filters) {
|
||||||
|
this.reOrganizeLabel(rule.filters);
|
||||||
this.setFilter(rule.filters);
|
this.setFilter(rule.filters);
|
||||||
this.updateFilter(rule.filters);
|
this.updateFilter(rule.filters);
|
||||||
}
|
}
|
||||||
@ -290,6 +307,46 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
setTimeout(() => clearInterval(hnd), 2000);
|
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[2]) {
|
||||||
|
if (!data.value.deleted) {
|
||||||
|
count++;
|
||||||
|
this.filterLabelInfo.push(data.value);
|
||||||
|
}
|
||||||
|
if (data.value.deleted) {
|
||||||
|
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[2]);
|
||||||
|
if (lab) {filterLabels.splice(filterLabels.indexOf(lab), 1); }
|
||||||
|
}
|
||||||
|
filterLabels.push({kind: 'label', value: count + ' labels'});
|
||||||
|
this.labelInputVal = count.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get projects(): FormArray {
|
get projects(): FormArray {
|
||||||
return this.ruleForm.get("projects") as FormArray;
|
return this.ruleForm.get("projects") as FormArray;
|
||||||
}
|
}
|
||||||
@ -320,16 +377,17 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
initFilter(name: string) {
|
initFilter(name: string) {
|
||||||
return this.fb.group({
|
return this.fb.group({
|
||||||
kind: name,
|
kind: name,
|
||||||
pattern: ["", Validators.required]
|
value: ['', Validators.required]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChange($event: any) {
|
filterChange($event: any, selectedValue: string) {
|
||||||
if ($event && $event.target["value"]) {
|
if ($event && $event.target["value"]) {
|
||||||
let id: number = $event.target.id;
|
let id: number = $event.target.id;
|
||||||
let name: string = $event.target.name;
|
let name: string = $event.target.name;
|
||||||
let value: string = $event.target["value"];
|
let value: string = $event.target["value"];
|
||||||
|
|
||||||
|
const controlArray = <FormArray> this.ruleForm.get('filters');
|
||||||
this.filterListData.forEach((data, index) => {
|
this.filterListData.forEach((data, index) => {
|
||||||
if (index === +id) {
|
if (index === +id) {
|
||||||
data.name = $event.target.name = value;
|
data.name = $event.target.name = value;
|
||||||
@ -339,6 +397,38 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
if (data.options.indexOf(name) === -1) {
|
if (data.options.indexOf(name) === -1) {
|
||||||
data.options.push(name);
|
data.options.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if before select, $event is label
|
||||||
|
if (!this.withAdmiral && name === this.filterSelect[2] && 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[2]) {
|
||||||
|
this.filterListData.forEach((data, index) => {
|
||||||
|
if (index === indexId) {
|
||||||
|
data.isOpen = true;
|
||||||
|
}else {
|
||||||
|
data.isOpen = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,15 +488,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addNewFilter(): void {
|
addNewFilter(): void {
|
||||||
|
const controlArray = <FormArray> this.ruleForm.get('filters');
|
||||||
if (this.filterCount === 0) {
|
if (this.filterCount === 0) {
|
||||||
this.filterListData.push(
|
this.filterListData.push(
|
||||||
this.baseFilterData(
|
this.baseFilterData(
|
||||||
this.filterSelect[0],
|
this.filterSelect[0],
|
||||||
this.filterSelect.slice(),
|
this.filterSelect.slice(),
|
||||||
true
|
true,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
this.filters.push(this.initFilter(this.filterSelect[0]));
|
controlArray.push(this.initFilter(this.filterSelect[0]));
|
||||||
} else {
|
} else {
|
||||||
let nameArr: string[] = this.filterSelect.slice();
|
let nameArr: string[] = this.filterSelect.slice();
|
||||||
this.filterListData.forEach(data => {
|
this.filterListData.forEach(data => {
|
||||||
@ -417,32 +508,40 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
data.options.splice(data.options.indexOf(nameArr[0]), 1);
|
data.options.splice(data.options.indexOf(nameArr[0]), 1);
|
||||||
});
|
});
|
||||||
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
|
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
|
||||||
this.filters.push(this.initFilter(nameArr[0]));
|
controlArray.push(this.initFilter(nameArr[0]));
|
||||||
}
|
}
|
||||||
this.filterCount += 1;
|
this.filterCount += 1;
|
||||||
if (this.filterCount >= this.filterSelect.length) {
|
if (this.filterCount >= this.filterSelect.length) {
|
||||||
this.isFilterHide = true;
|
this.isFilterHide = true;
|
||||||
}
|
}
|
||||||
|
if (controlArray.controls[this.filterCount - 1].get('kind').value === this.filterSelect[2] && this.labelInputVal) {
|
||||||
|
controlArray.controls[this.filterCount - 1].get('value').setValue(this.labelInputVal + ' labels');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete a filter
|
// delete a filter
|
||||||
deleteFilter(i: number): void {
|
deleteFilter(i: number): void {
|
||||||
if (i || i === 0) {
|
if (i >= 0) {
|
||||||
let delfilter = this.filterListData.splice(i, 1)[0];
|
let delFilter = this.filterListData.splice(i, 1)[0];
|
||||||
if (this.filterCount === this.filterSelect.length) {
|
if (this.filterCount === this.filterSelect.length) {
|
||||||
this.isFilterHide = false;
|
this.isFilterHide = false;
|
||||||
}
|
}
|
||||||
this.filterCount -= 1;
|
this.filterCount -= 1;
|
||||||
if (this.filterListData.length) {
|
if (this.filterListData.length) {
|
||||||
let optionVal = delfilter.name;
|
let optionVal = delFilter.name;
|
||||||
this.filterListData.filter(data => {
|
this.filterListData.filter(data => {
|
||||||
if (data.options.indexOf(optionVal) === -1) {
|
if (data.options.indexOf(optionVal) === -1) {
|
||||||
data.options.push(optionVal);
|
data.options.push(optionVal);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const control = <FormArray>this.ruleForm.controls["filters"];
|
const control = <FormArray>this.ruleForm.get('filters');
|
||||||
|
if (control.controls[i].get('kind').value === this.filterSelect[2]) {
|
||||||
|
this.labelInputVal = control.controls[i].get('value').value.split(' ')[0];
|
||||||
|
}
|
||||||
control.removeAt(i);
|
control.removeAt(i);
|
||||||
|
this.setFilter(control.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,6 +609,31 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateTrigger(trigger: any) {
|
updateTrigger(trigger: any) {
|
||||||
if (trigger["schedule_param"]) {
|
if (trigger["schedule_param"]) {
|
||||||
this.isScheduleOpt = true;
|
this.isScheduleOpt = true;
|
||||||
@ -558,6 +682,19 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
public hasFormChange(): boolean {
|
||||||
return !isEmptyObject(this.getChanges());
|
return !isEmptyObject(this.getChanges());
|
||||||
}
|
}
|
||||||
@ -567,6 +704,9 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
let copyRuleForm: ReplicationRule = this.ruleForm.value;
|
let copyRuleForm: ReplicationRule = this.ruleForm.value;
|
||||||
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
|
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
|
||||||
|
// rewrite key name of label when filer contain labels.
|
||||||
|
if (copyRuleForm.filters) { this.setFilterLabelVal(copyRuleForm.filters); };
|
||||||
|
|
||||||
if (this.policyId < 0) {
|
if (this.policyId < 0) {
|
||||||
this.repService
|
this.repService
|
||||||
.createReplicationRule(copyRuleForm)
|
.createReplicationRule(copyRuleForm)
|
||||||
@ -602,19 +742,24 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
openCreateEditRule(ruleId?: number | string): void {
|
openCreateEditRule(ruleId?: number | string): void {
|
||||||
this.initForm();
|
this.initForm();
|
||||||
|
this.inlineAlert.close();
|
||||||
this.selectedProjectList = [];
|
this.selectedProjectList = [];
|
||||||
this.filterCount = 0;
|
this.filterCount = 0;
|
||||||
|
this.isFilterHide = false;
|
||||||
this.filterListData = [];
|
this.filterListData = [];
|
||||||
this.firstClick = 0;
|
this.firstClick = 0;
|
||||||
this.noSelectedProject = true;
|
this.noSelectedProject = true;
|
||||||
this.noSelectedEndpoint = true;
|
this.noSelectedEndpoint = true;
|
||||||
this.isRuleNameValid = true;
|
this.isRuleNameValid = true;
|
||||||
|
this.deletedLabelCount = 0;
|
||||||
|
|
||||||
this.weeklySchedule = false;
|
this.weeklySchedule = false;
|
||||||
this.isScheduleOpt = false;
|
this.isScheduleOpt = false;
|
||||||
this.isImmediate = false;
|
this.isImmediate = false;
|
||||||
this.policyId = -1;
|
this.policyId = -1;
|
||||||
this.createEditRuleOpened = true;
|
this.createEditRuleOpened = true;
|
||||||
|
this.filterLabelInfo = [];
|
||||||
|
this.labelInputVal = '';
|
||||||
|
|
||||||
this.noProjectInfo = "";
|
this.noProjectInfo = "";
|
||||||
this.noEndpointInfo = "";
|
this.noEndpointInfo = "";
|
||||||
@ -630,12 +775,12 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
this.headerTitle = "REPLICATION.EDIT_POLICY_TITLE";
|
this.headerTitle = "REPLICATION.EDIT_POLICY_TITLE";
|
||||||
toPromise(this.repService.getReplicationRule(ruleId))
|
toPromise(this.repService.getReplicationRule(ruleId))
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.copyUpdateForm = Object.assign({}, response);
|
this.copyUpdateForm = clone(response);
|
||||||
// set filter value is [] if callback fiter value is null.
|
// set filter value is [] if callback filter value is null.
|
||||||
this.copyUpdateForm.filters = response.filters
|
|
||||||
? response.filters
|
|
||||||
: [];
|
|
||||||
this.updateForm(response);
|
this.updateForm(response);
|
||||||
|
// keep trigger same value
|
||||||
|
this.copyUpdateForm.trigger = clone(response.trigger);
|
||||||
|
this.copyUpdateForm.filters = this.copyUpdateForm.filters === null ? [] : this.copyUpdateForm.filters;
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
this.inlineAlert.showInlineError(error);
|
this.inlineAlert.showInlineError(error);
|
||||||
@ -648,8 +793,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
]);
|
]);
|
||||||
this.noSelectedProject = false;
|
this.noSelectedProject = false;
|
||||||
}
|
}
|
||||||
|
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||||
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,7 +887,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
getChanges(): { [key: string]: any | any[] } {
|
getChanges(): { [key: string]: any | any[] } {
|
||||||
let changes: { [key: string]: any | any[] } = {};
|
let changes: { [key: string]: any | any[] } = {};
|
||||||
let ruleValue: { [key: string]: any | any[] } = this.ruleForm.value;
|
let ruleValue: { [key: string]: any | any[] } = clone(this.ruleForm.value);
|
||||||
|
if (ruleValue.filters && ruleValue.filters.length) {
|
||||||
|
ruleValue.filters.forEach((data, index) => {
|
||||||
|
if (data.kind === this.filterSelect[2]) {
|
||||||
|
ruleValue.filters.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// rewrite filter label
|
||||||
|
this.filterLabelInfo.forEach(data => {
|
||||||
|
ruleValue.filters.push({kind: "label", pattern: "", value: data});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (!ruleValue || !this.copyUpdateForm) {
|
if (!ruleValue || !this.copyUpdateForm) {
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<div class="filterLabelPanel" [hidden]="!openFilterLabelPanel">
|
||||||
|
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||||
|
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>
|
||||||
|
<div><input class="filterInput" type="text" placeholder="Filter labels" [(ngModel)]= "filterLabelName" (keyup)="handleInputFilter()"></div>
|
||||||
|
<div [hidden]='labelLists.length' style="padding-left:10px;">{{'LABEL.NO_LABELS' | translate }}</div>
|
||||||
|
|
||||||
|
<div [hidden]='!labelLists.length' style='max-height:300px;overflow-y: auto;'>
|
||||||
|
<button type="button" class="labelBtn" *ngFor='let label of labelLists' [hidden]='!label.show' (click)="selectLabel(label)">
|
||||||
|
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
|
||||||
|
<div class='labelDiv'><hbr-label-piece [label]="label.label" [labelWidth]="118"></hbr-label-piece></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
132
src/ui_ng/lib/src/create-edit-rule/filter-label.component.scss
Normal file
132
src/ui_ng/lib/src/create-edit-rule/filter-label.component.scss
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
.filterLabelPanel {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
left:135px;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
padding: .5rem 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 1px 0.125rem hsla(0, 0%, 45%, .25);
|
||||||
|
min-width: 5rem;
|
||||||
|
max-width: 15rem;
|
||||||
|
border-radius: .125rem;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterLabelPanel .filterInput{margin-left: .5rem; margin-right: .5rem;}
|
||||||
|
|
||||||
|
.filterLabelHeader {
|
||||||
|
font-size: .5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: normal;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
line-height: .75rem;
|
||||||
|
margin: 0;
|
||||||
|
color: #313131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterLabelPanel .form-group input {
|
||||||
|
position: relative;
|
||||||
|
margin-left: .5rem;
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterClose {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-left {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 15px;
|
||||||
|
min-width: 15px;
|
||||||
|
color: black;
|
||||||
|
vertical-align: super;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-item,
|
||||||
|
.signpost-item {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signpost-content-body .label {
|
||||||
|
margin: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelDiv {
|
||||||
|
position: absolute;
|
||||||
|
left: 28px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datagrid-action-bar {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-item hbr-label-piece {
|
||||||
|
display: flex !important;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host>>>.signpost-content {
|
||||||
|
min-width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host>>>.signpost-content-body {
|
||||||
|
padding: 0 .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host>>>.signpost-content-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterLabelPiece {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .dropdown-toggle.btn {
|
||||||
|
margin: .25rem .5rem .25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelBtn {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: .58333rem;
|
||||||
|
letter-spacing: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #565656;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelBtn:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelBtn:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelDiv label{margin: 0;}
|
170
src/ui_ng/lib/src/create-edit-rule/filter-label.component.ts
Normal file
170
src/ui_ng/lib/src/create-edit-rule/filter-label.component.ts
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import {Component, Input, OnInit, OnChanges, Output, EventEmitter, ChangeDetectorRef, SimpleChanges} from "@angular/core";
|
||||||
|
import {LabelService} from "../service/label.service";
|
||||||
|
import {toPromise} from "../utils";
|
||||||
|
import {Label} from "../service/interface";
|
||||||
|
import {ErrorHandler} from "../error-handler/error-handler";
|
||||||
|
import {Subject} from "rxjs/Subject";
|
||||||
|
|
||||||
|
export interface LabelState {
|
||||||
|
iconsShow: boolean;
|
||||||
|
label: Label;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "hbr-filter-label",
|
||||||
|
templateUrl: "./filter-label.component.html",
|
||||||
|
styleUrls: ["./filter-label.component.scss"]
|
||||||
|
})
|
||||||
|
export class FilterLabelComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
|
openFilterLabelPanel: boolean;
|
||||||
|
labelLists: LabelState[] = [];
|
||||||
|
filterLabelName = '';
|
||||||
|
labelNameFilter: Subject<string> = new Subject<string> ();
|
||||||
|
@Input() isOpen: boolean;
|
||||||
|
@Input() projectId: number;
|
||||||
|
@Input() selectedLabelInfo: Label[];
|
||||||
|
@Output() selectedLabels = new EventEmitter<LabelState[]>();
|
||||||
|
@Output() closePanelEvent = new EventEmitter();
|
||||||
|
|
||||||
|
constructor(private labelService: LabelService,
|
||||||
|
private ref: ChangeDetectorRef,
|
||||||
|
private errorHandler: ErrorHandler) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
Promise.all([this.getGLabels(), this.getPLabels()]).then(() => {
|
||||||
|
this.selectedLabelInfo.forEach(info => {
|
||||||
|
if (this.labelLists.length) {
|
||||||
|
let lab = this.labelLists.find(data => data.label.id === info.id);
|
||||||
|
if (lab) {this.selectOper(lab); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.labelNameFilter
|
||||||
|
.debounceTime(500)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe((name: string) => {
|
||||||
|
if (this.filterLabelName.length) {
|
||||||
|
|
||||||
|
this.labelLists.forEach(data => {
|
||||||
|
if (data.label.name.indexOf(this.filterLabelName) !== -1) {
|
||||||
|
data.show = true;
|
||||||
|
} else {
|
||||||
|
data.show = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
setInterval(() => this.ref.markForCheck(), 200);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['isOpen']) {this.openFilterLabelPanel = changes['isOpen'].currentValue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
getGLabels() {
|
||||||
|
return toPromise<Label[]>(this.labelService.getGLabels()).then((res: Label[]) => {
|
||||||
|
if (res.length) {
|
||||||
|
res.forEach(data => {
|
||||||
|
this.labelLists.push({'iconsShow': false, 'label': data, 'show': true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPLabels() {
|
||||||
|
if (this.projectId && this.projectId > 0) {
|
||||||
|
return toPromise<Label[]>(this.labelService.getPLabels(this.projectId)).then((res1: Label[]) => {
|
||||||
|
if (res1.length) {
|
||||||
|
res1.forEach(data => {
|
||||||
|
this.labelLists.push({'iconsShow': false, 'label': data, 'show': true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputFilter(): void {
|
||||||
|
if (this.filterLabelName.length) {
|
||||||
|
this.labelNameFilter.next(this.filterLabelName);
|
||||||
|
}else {
|
||||||
|
this.labelLists.every(data => data.show = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectLabel(labelInfo: LabelState): void {
|
||||||
|
if (labelInfo) {
|
||||||
|
let isClick = true;
|
||||||
|
if (!labelInfo.iconsShow) {
|
||||||
|
this.selectOper(labelInfo, isClick);
|
||||||
|
} else {
|
||||||
|
this.unSelectOper(labelInfo, isClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOper(labelInfo: LabelState, isClick?: boolean): void {
|
||||||
|
// set the selected label in front
|
||||||
|
this.labelLists.splice(this.labelLists.indexOf(labelInfo), 1);
|
||||||
|
this.labelLists.some((data, i) => {
|
||||||
|
if (!data.iconsShow) {
|
||||||
|
this.labelLists.splice(i, 0, labelInfo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// when is the last one
|
||||||
|
if (this.labelLists.every(data => data.iconsShow === true)) {
|
||||||
|
this.labelLists.push(labelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
labelInfo.iconsShow = true;
|
||||||
|
if (isClick) {
|
||||||
|
this.selectedLabels.emit(this.labelLists);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unSelectOper(labelInfo: LabelState, isClick?: boolean): void {
|
||||||
|
this.sortOperation(this.labelLists, labelInfo);
|
||||||
|
|
||||||
|
labelInfo.iconsShow = false;
|
||||||
|
if (isClick) {
|
||||||
|
this.selectedLabels.emit(this.labelLists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the unselected label to groups with the same icons
|
||||||
|
sortOperation(labelList: LabelState[], labelInfo: LabelState): void {
|
||||||
|
labelList.some((data, i) => {
|
||||||
|
if (!data.iconsShow) {
|
||||||
|
if (data.label.scope === labelInfo.label.scope) {
|
||||||
|
labelList.splice(i, 0, labelInfo);
|
||||||
|
labelList.splice(labelList.indexOf(labelInfo, 0), 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (data.label.scope !== labelInfo.label.scope && i === labelList.length - 1) {
|
||||||
|
labelList.push(labelInfo);
|
||||||
|
labelList.splice(labelList.indexOf(labelInfo), 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.iconsShow && i === labelList.length - 1) {
|
||||||
|
labelList.push(labelInfo);
|
||||||
|
labelList.splice(labelList.indexOf(labelInfo), 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeFilter(): void {
|
||||||
|
this.closePanelEvent.emit();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import { Type } from "@angular/core";
|
import { Type } from "@angular/core";
|
||||||
|
|
||||||
import { CreateEditRuleComponent } from "./create-edit-rule.component";
|
import { CreateEditRuleComponent } from "./create-edit-rule.component";
|
||||||
|
import {FilterLabelComponent} from "./filter-label.component";
|
||||||
|
|
||||||
export const CREATE_EDIT_RULE_DIRECTIVES: Type<any>[] = [
|
export const CREATE_EDIT_RULE_DIRECTIVES: Type<any>[] = [
|
||||||
CreateEditRuleComponent
|
CreateEditRuleComponent,
|
||||||
|
FilterLabelComponent
|
||||||
];
|
];
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
color: #222;
|
color: #222;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
line-height: .875rem;
|
line-height: .875rem;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<div style="padding-bottom: 15px;">
|
<div style="padding-bottom: 15px;">
|
||||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" [clDgRowSelection]="true">
|
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" [clDgRowSelection]="true">
|
||||||
<clr-dg-action-bar style="height: 24px;">
|
<clr-dg-action-bar>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon> {{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
|
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon> {{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon> {{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon> {{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon> {{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon> {{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon> {{'REPLICATION.REPLICATE' | translate}}</button>
|
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon> {{'REPLICATION.REPLICATE' | translate}}</button>
|
||||||
</clr-dg-action-bar>
|
</clr-dg-action-bar>
|
||||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
<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 [clrDgField]="'projects'" *ngIf="!projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'projects'" *ngIf="!projectScope">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'targets'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'targets'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||||
@ -14,6 +15,17 @@
|
|||||||
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
<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-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>{{p.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div [ngSwitch]="hasDeletedLabel(p)">
|
||||||
|
<clr-tooltip *ngSwitchCase="'disabled'" class="tooltip-lg">
|
||||||
|
<clr-icon clrTooltipTrigger shape="exclamation-triangle" style="vertical-align: text-bottom;" class="is-warning" 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" style="vertical-align: text-bottom;" class="is-success" size="18"></clr-icon> Enabled</div>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="!projectScope">
|
<clr-dg-cell *ngIf="!projectScope">
|
||||||
<a href="javascript:void(0)" (click)="$event.stopPropagation(); redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
|
<a href="javascript:void(0)" (click)="$event.stopPropagation(); redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
@ -152,6 +152,21 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
|||||||
this.replicateManual.emit(rules);
|
this.replicateManual.emit(rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasDeletedLabel(rule: any) {
|
||||||
|
if (rule.filters) {
|
||||||
|
let count = 0;
|
||||||
|
rule.filters.forEach((data: any) => {
|
||||||
|
if (data.kind === 'label' && data.value.deleted) {
|
||||||
|
count ++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (count === 0) {
|
||||||
|
return 'enabled';
|
||||||
|
}else { return 'disabled'; }
|
||||||
|
}
|
||||||
|
return 'enabled';
|
||||||
|
}
|
||||||
|
|
||||||
deletionConfirm(message: ConfirmationAcknowledgement) {
|
deletionConfirm(message: ConfirmationAcknowledgement) {
|
||||||
if (
|
if (
|
||||||
message &&
|
message &&
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<job-log-viewer #replicationLogViewer></job-log-viewer>
|
<job-log-viewer #replicationLogViewer></job-log-viewer>
|
||||||
<hbr-create-edit-rule *ngIf="isSystemAdmin" [projectId]="projectId" [projectName]="projectName" (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
|
<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 #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.componen
|
|||||||
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
|
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
|
||||||
import {ProjectDefaultService, ProjectService} from "../service/project.service";
|
import {ProjectDefaultService, ProjectService} from "../service/project.service";
|
||||||
import {OperationService} from "../operation/operation.service";
|
import {OperationService} from "../operation/operation.service";
|
||||||
|
import {FilterLabelComponent} from "../create-edit-rule/filter-label.component";
|
||||||
|
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||||
|
|
||||||
describe('Replication Component (inline template)', () => {
|
describe('Replication Component (inline template)', () => {
|
||||||
|
|
||||||
@ -224,7 +226,9 @@ describe('Replication Component (inline template)', () => {
|
|||||||
DatePickerComponent,
|
DatePickerComponent,
|
||||||
FilterComponent,
|
FilterComponent,
|
||||||
InlineAlertComponent,
|
InlineAlertComponent,
|
||||||
JobLogViewerComponent
|
JobLogViewerComponent,
|
||||||
|
FilterLabelComponent,
|
||||||
|
LabelPieceComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
|
@ -105,6 +105,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
|||||||
@Input() projectId: number | string;
|
@Input() projectId: number | string;
|
||||||
@Input() projectName: string;
|
@Input() projectName: string;
|
||||||
@Input() isSystemAdmin: boolean;
|
@Input() isSystemAdmin: boolean;
|
||||||
|
@Input() withAdmiral: boolean;
|
||||||
@Input() withReplicationJob: boolean;
|
@Input() withReplicationJob: boolean;
|
||||||
|
|
||||||
@Output() redirect = new EventEmitter<ReplicationRule>();
|
@Output() redirect = new EventEmitter<ReplicationRule>();
|
||||||
|
@ -20,7 +20,7 @@ export abstract class LabelService {
|
|||||||
|
|
||||||
abstract getLabels(
|
abstract getLabels(
|
||||||
scope: string,
|
scope: string,
|
||||||
projectId: number,
|
projectId?: number,
|
||||||
name?: string,
|
name?: string,
|
||||||
queryParams?: RequestQueryParams
|
queryParams?: RequestQueryParams
|
||||||
): Observable<Label[]> | Promise<Label[]>;
|
): Observable<Label[]> | Promise<Label[]>;
|
||||||
@ -55,7 +55,7 @@ export class LabelDefaultService extends LabelService {
|
|||||||
|
|
||||||
getLabels(
|
getLabels(
|
||||||
scope: string,
|
scope: string,
|
||||||
projectId: number,
|
projectId?: number,
|
||||||
name?: string,
|
name?: string,
|
||||||
queryParams?: RequestQueryParams
|
queryParams?: RequestQueryParams
|
||||||
): Observable<Label[]> | Promise<Label[]> {
|
): Observable<Label[]> | Promise<Label[]> {
|
||||||
|
@ -350,6 +350,11 @@ export class TagComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// when is the last one
|
||||||
|
if (this.imageStickLabels.every(data => data.iconsShow === true)) {
|
||||||
|
this.imageStickLabels.push(labelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
labelInfo.iconsShow = true;
|
labelInfo.iconsShow = true;
|
||||||
this.inprogress = false;
|
this.inprogress = false;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
"bootstrap": "4.0.0-alpha.5",
|
"bootstrap": "4.0.0-alpha.5",
|
||||||
"codelyzer": "~2.0.0-beta.4",
|
"codelyzer": "~2.0.0-beta.4",
|
||||||
"enhanced-resolve": "^3.0.0",
|
"enhanced-resolve": "^3.0.0",
|
||||||
"harbor-ui": "0.7.19-test-14",
|
"harbor-ui": "0.7.19-test-16",
|
||||||
"jasmine-core": "2.4.1",
|
"jasmine-core": "2.4.1",
|
||||||
"jasmine-spec-reporter": "2.5.0",
|
"jasmine-spec-reporter": "2.5.0",
|
||||||
"karma": "~1.7.0",
|
"karma": "~1.7.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
|
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
|
||||||
<div style="margin-top: 4px;">
|
<div style="margin-top: 4px;">
|
||||||
<hbr-replication [withReplicationJob]='true' [isSystemAdmin]="isSystemAdmin" (goToRegistry)="goRegistry()" (redirect)="customRedirect($event)"></hbr-replication>
|
<hbr-replication [withReplicationJob]='true' [isSystemAdmin]="isSystemAdmin" [withAdmiral]="withAdmiral" (goToRegistry)="goRegistry()" (redirect)="customRedirect($event)"></hbr-replication>
|
||||||
</div>
|
</div>
|
@ -17,6 +17,7 @@ import {Router, ActivatedRoute} from "@angular/router";
|
|||||||
import {ReplicationRule} from "harbor-ui";
|
import {ReplicationRule} from "harbor-ui";
|
||||||
|
|
||||||
import {SessionService} from "../../shared/session.service";
|
import {SessionService} from "../../shared/session.service";
|
||||||
|
import {AppConfigService} from "../../app-config.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'total-replication',
|
selector: 'total-replication',
|
||||||
@ -26,6 +27,7 @@ export class TotalReplicationPageComponent {
|
|||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private session: SessionService,
|
private session: SessionService,
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
private activeRoute: ActivatedRoute) {}
|
private activeRoute: ActivatedRoute) {}
|
||||||
customRedirect(rule: ReplicationRule): void {
|
customRedirect(rule: ReplicationRule): void {
|
||||||
if (rule) {
|
if (rule) {
|
||||||
@ -40,4 +42,8 @@ export class TotalReplicationPageComponent {
|
|||||||
let account = this.session.getCurrentUser();
|
let account = this.session.getCurrentUser();
|
||||||
return account != null && account.has_admin_role;
|
return account != null && account.has_admin_role;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get withAdmiral(): boolean {
|
||||||
|
return this.appConfigService.getConfig().with_admiral;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,6 +272,7 @@
|
|||||||
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
|
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
|
||||||
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
|
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
|
||||||
"NAME": "Name",
|
"NAME": "Name",
|
||||||
|
"STATUS": "Status",
|
||||||
"PROJECT": "Project",
|
"PROJECT": "Project",
|
||||||
"NAME_IS_REQUIRED": "Name is required.",
|
"NAME_IS_REQUIRED": "Name is required.",
|
||||||
"DESCRIPTION": "Description",
|
"DESCRIPTION": "Description",
|
||||||
@ -344,7 +345,10 @@
|
|||||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||||
"NEW": "New",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "New Endpoint",
|
"NEW_ENDPOINT": "New Endpoint",
|
||||||
|
@ -271,6 +271,7 @@
|
|||||||
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
|
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
|
||||||
"TEST_CONNECTION_FAILURE": "Fallo al conectar con el endpoint.",
|
"TEST_CONNECTION_FAILURE": "Fallo al conectar con el endpoint.",
|
||||||
"NAME": "Nombre",
|
"NAME": "Nombre",
|
||||||
|
"STATUS": "Status",
|
||||||
"PROJECT": "Proyecto",
|
"PROJECT": "Proyecto",
|
||||||
"NAME_IS_REQUIRED": "El nombre es obligatorio.",
|
"NAME_IS_REQUIRED": "El nombre es obligatorio.",
|
||||||
"DESCRIPTION": "Descripción",
|
"DESCRIPTION": "Descripción",
|
||||||
@ -343,7 +344,10 @@
|
|||||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||||
"NEW": "New",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "Nuevo Endpoint",
|
"NEW_ENDPOINT": "Nuevo Endpoint",
|
||||||
|
@ -250,6 +250,7 @@
|
|||||||
"TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.",
|
"TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.",
|
||||||
"TEST_CONNECTION_FAILURE": "Echec du ping du point final.",
|
"TEST_CONNECTION_FAILURE": "Echec du ping du point final.",
|
||||||
"NAME": "Nom",
|
"NAME": "Nom",
|
||||||
|
"STATUS": "Status",
|
||||||
"PROJECT": "Projet",
|
"PROJECT": "Projet",
|
||||||
"NAME_IS_REQUIRED": "Le nom est obligatoire.",
|
"NAME_IS_REQUIRED": "Le nom est obligatoire.",
|
||||||
"DESCRIPTION": "Description",
|
"DESCRIPTION": "Description",
|
||||||
@ -321,7 +322,10 @@
|
|||||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||||
"NEW": "New",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "Nouveau Point Final",
|
"NEW_ENDPOINT": "Nouveau Point Final",
|
||||||
|
@ -271,6 +271,7 @@
|
|||||||
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
|
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
|
||||||
"TEST_CONNECTION_FAILURE": "测试连接失败。",
|
"TEST_CONNECTION_FAILURE": "测试连接失败。",
|
||||||
"NAME": "名称",
|
"NAME": "名称",
|
||||||
|
"STATUS": "状态",
|
||||||
"PROJECT": "项目",
|
"PROJECT": "项目",
|
||||||
"NAME_IS_REQUIRED": "名称为必填项。",
|
"NAME_IS_REQUIRED": "名称为必填项。",
|
||||||
"DESCRIPTION": "描述",
|
"DESCRIPTION": "描述",
|
||||||
@ -343,7 +344,10 @@
|
|||||||
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。",
|
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。",
|
||||||
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。",
|
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。",
|
||||||
"NEW": "新增",
|
"NEW": "新增",
|
||||||
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。"
|
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||||
|
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
|
||||||
|
"ACKNOWLEDGE": "确认",
|
||||||
|
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。"
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "新建目标",
|
"NEW_ENDPOINT": "新建目标",
|
||||||
|
Loading…
Reference in New Issue
Block a user