mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +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>
|
||||
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
|
||||
<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>
|
||||
<section class="form-block">
|
||||
<div class="form-group form-group-override">
|
||||
@ -39,23 +47,28 @@
|
||||
<div class="form-group form-group-override">
|
||||
<label class="form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
|
||||
<div formArrayName="filters">
|
||||
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index" [formGroupName]="i">
|
||||
<div>
|
||||
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index">
|
||||
<div [formGroupName]="i">
|
||||
<div class="select floatSetPar">
|
||||
<select formControlName="kind" (change)="filterChange($event)" id="{{i}}" name="{{filterListData[i]?.name}}">
|
||||
<option *ngFor="let filter of filterListData[i]?.options;" value="{{filter}}">{{filter}}</option>
|
||||
<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>
|
||||
<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'>
|
||||
<input type="text" #filterValue required size="14" formControlName="pattern">
|
||||
[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="value" [attr.disabled]="(filterListData[i]?.name=='label') ?'' : null">
|
||||
<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" 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>
|
||||
<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>
|
||||
<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>
|
||||
<!--Targets-->
|
||||
<div class="form-group form-group-override">
|
||||
|
@ -44,6 +44,7 @@ h4 {
|
||||
}
|
||||
|
||||
.filterSelect {
|
||||
position: relative;
|
||||
width: 315px;
|
||||
}
|
||||
|
||||
@ -156,8 +157,29 @@ h4 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.arrowSet{
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 8px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
clr-modal {
|
||||
::ng-deep div.modal-dialog {
|
||||
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";
|
||||
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
|
||||
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)", () => {
|
||||
let mockRules: ReplicationRule[] = [
|
||||
@ -239,7 +242,9 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
||||
DatePickerComponent,
|
||||
FilterComponent,
|
||||
InlineAlertComponent,
|
||||
JobLogViewerComponent
|
||||
JobLogViewerComponent,
|
||||
FilterLabelComponent,
|
||||
LabelPieceComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
@ -248,7 +253,8 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
{ provide: JobLogService, useClass: JobLogDefaultService },
|
||||
{ provide: OperationService }
|
||||
{ provide: OperationService },
|
||||
{ provide: LabelService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -21,11 +21,11 @@ import {
|
||||
EventEmitter,
|
||||
Output
|
||||
} from "@angular/core";
|
||||
import { Filter, ReplicationRule, Endpoint } from "../service/interface";
|
||||
import {Filter, ReplicationRule, Endpoint, Label} from "../service/interface";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
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 { ReplicationService } from "../service/replication.service";
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
@ -33,6 +33,7 @@ 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;
|
||||
@ -56,6 +57,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
noSelectedProject = true;
|
||||
noSelectedEndpoint = true;
|
||||
filterCount = 0;
|
||||
alertClosed = false;
|
||||
triggerNames: string[] = ["Manual", "Immediate", "Scheduled"];
|
||||
scheduleNames: string[] = ["Daily", "Weekly"];
|
||||
weekly: string[] = [
|
||||
@ -67,7 +69,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
"Saturday",
|
||||
"Sunday"
|
||||
];
|
||||
filterSelect: string[] = ["repository", "tag"];
|
||||
filterSelect: string[] = ["repository", "tag", "label"];
|
||||
ruleNameTooltip = "REPLICATION.NAME_TOOLTIP";
|
||||
headerTitle = "REPLICATION.ADD_POLICY";
|
||||
|
||||
@ -80,13 +82,19 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
proNameChecker: 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;
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@Input() withAdmiral: boolean;
|
||||
|
||||
@Output() goToRegistry = new EventEmitter<any>();
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
@ -123,11 +131,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
name: name,
|
||||
options: option,
|
||||
state: state,
|
||||
isValid: true
|
||||
isValid: true,
|
||||
isOpen: false // label list
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.withAdmiral) {
|
||||
this.filterSelect = ["repository", "tag"];
|
||||
}
|
||||
|
||||
toPromise<Endpoint[]>(this.endpointService.getEndpoints())
|
||||
.then(targets => {
|
||||
this.targetList = targets || [];
|
||||
@ -173,7 +186,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.proNameChecker
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
.subscribe((name: string) => {
|
||||
.subscribe((resp: string) => {
|
||||
let name = this.ruleForm.controls["projects"].value[0].name;
|
||||
this.noProjectInfo = "";
|
||||
this.selectedProjectList = [];
|
||||
toPromise<Project[]>(this.proService.listProjects(name, undefined))
|
||||
@ -225,6 +239,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
createForm() {
|
||||
this.formArrayLabel = this.fb.array([]);
|
||||
|
||||
this.ruleForm = this.fb.group({
|
||||
name: ["", Validators.required],
|
||||
description: "",
|
||||
@ -263,7 +279,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.setTarget([this.emptyEndpoint]);
|
||||
this.setFilter([]);
|
||||
|
||||
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
|
||||
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||
}
|
||||
|
||||
updateForm(rule: ReplicationRule): void {
|
||||
@ -281,6 +297,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.noSelectedEndpoint = false;
|
||||
|
||||
if (rule.filters) {
|
||||
this.reOrganizeLabel(rule.filters);
|
||||
this.setFilter(rule.filters);
|
||||
this.updateFilter(rule.filters);
|
||||
}
|
||||
@ -290,6 +307,46 @@ 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[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 {
|
||||
return this.ruleForm.get("projects") as FormArray;
|
||||
}
|
||||
@ -320,16 +377,17 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
initFilter(name: string) {
|
||||
return this.fb.group({
|
||||
kind: name,
|
||||
pattern: ["", Validators.required]
|
||||
value: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
filterChange($event: any) {
|
||||
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;
|
||||
@ -339,6 +397,38 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
if (data.options.indexOf(name) === -1) {
|
||||
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 {
|
||||
const controlArray = <FormArray> this.ruleForm.get('filters');
|
||||
if (this.filterCount === 0) {
|
||||
this.filterListData.push(
|
||||
this.baseFilterData(
|
||||
this.filterSelect[0],
|
||||
this.filterSelect.slice(),
|
||||
true
|
||||
true,
|
||||
)
|
||||
);
|
||||
this.filters.push(this.initFilter(this.filterSelect[0]));
|
||||
controlArray.push(this.initFilter(this.filterSelect[0]));
|
||||
} else {
|
||||
let nameArr: string[] = this.filterSelect.slice();
|
||||
this.filterListData.forEach(data => {
|
||||
@ -417,32 +508,40 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
data.options.splice(data.options.indexOf(nameArr[0]), 1);
|
||||
});
|
||||
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;
|
||||
if (this.filterCount >= this.filterSelect.length) {
|
||||
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
|
||||
deleteFilter(i: number): void {
|
||||
if (i || i === 0) {
|
||||
let delfilter = this.filterListData.splice(i, 1)[0];
|
||||
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;
|
||||
let optionVal = delFilter.name;
|
||||
this.filterListData.filter(data => {
|
||||
if (data.options.indexOf(optionVal) === -1) {
|
||||
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);
|
||||
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) {
|
||||
if (trigger["schedule_param"]) {
|
||||
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 {
|
||||
return !isEmptyObject(this.getChanges());
|
||||
}
|
||||
@ -567,6 +704,9 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.inProgress = true;
|
||||
let copyRuleForm: ReplicationRule = this.ruleForm.value;
|
||||
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) {
|
||||
this.repService
|
||||
.createReplicationRule(copyRuleForm)
|
||||
@ -602,19 +742,24 @@ 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.isScheduleOpt = false;
|
||||
this.isImmediate = false;
|
||||
this.policyId = -1;
|
||||
this.createEditRuleOpened = true;
|
||||
this.filterLabelInfo = [];
|
||||
this.labelInputVal = '';
|
||||
|
||||
this.noProjectInfo = "";
|
||||
this.noEndpointInfo = "";
|
||||
@ -630,12 +775,12 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.headerTitle = "REPLICATION.EDIT_POLICY_TITLE";
|
||||
toPromise(this.repService.getReplicationRule(ruleId))
|
||||
.then(response => {
|
||||
this.copyUpdateForm = Object.assign({}, response);
|
||||
// set filter value is [] if callback fiter value is null.
|
||||
this.copyUpdateForm.filters = response.filters
|
||||
? response.filters
|
||||
: [];
|
||||
this.copyUpdateForm = clone(response);
|
||||
// set filter value is [] if callback filter value is null.
|
||||
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) => {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
@ -648,8 +793,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
]);
|
||||
this.noSelectedProject = false;
|
||||
}
|
||||
|
||||
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
|
||||
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -743,7 +887,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
getChanges(): { [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) {
|
||||
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 { CreateEditRuleComponent } from "./create-edit-rule.component";
|
||||
import {FilterLabelComponent} from "./filter-label.component";
|
||||
|
||||
export const CREATE_EDIT_RULE_DIRECTIVES: Type<any>[] = [
|
||||
CreateEditRuleComponent
|
||||
CreateEditRuleComponent,
|
||||
FilterLabelComponent
|
||||
];
|
||||
|
@ -3,6 +3,7 @@
|
||||
color: #222;
|
||||
display: inline-block;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: .875rem;
|
||||
|
@ -1,12 +1,13 @@
|
||||
<div style="padding-bottom: 15px;">
|
||||
<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" [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)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon> {{'REPLICATION.REPLICATE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [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]="'description'">{{'REPLICATION.DESCRIPTION' | 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-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>
|
||||
<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">
|
||||
<a href="javascript:void(0)" (click)="$event.stopPropagation(); redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
|
||||
</clr-dg-cell>
|
||||
|
@ -152,6 +152,21 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
||||
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) {
|
||||
if (
|
||||
message &&
|
||||
|
@ -72,7 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -21,6 +21,8 @@ import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.componen
|
||||
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
|
||||
import {ProjectDefaultService, ProjectService} from "../service/project.service";
|
||||
import {OperationService} from "../operation/operation.service";
|
||||
import {FilterLabelComponent} from "../create-edit-rule/filter-label.component";
|
||||
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||
|
||||
describe('Replication Component (inline template)', () => {
|
||||
|
||||
@ -224,7 +226,9 @@ describe('Replication Component (inline template)', () => {
|
||||
DatePickerComponent,
|
||||
FilterComponent,
|
||||
InlineAlertComponent,
|
||||
JobLogViewerComponent
|
||||
JobLogViewerComponent,
|
||||
FilterLabelComponent,
|
||||
LabelPieceComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
|
@ -105,6 +105,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
@Input() projectId: number | string;
|
||||
@Input() projectName: string;
|
||||
@Input() isSystemAdmin: boolean;
|
||||
@Input() withAdmiral: boolean;
|
||||
@Input() withReplicationJob: boolean;
|
||||
|
||||
@Output() redirect = new EventEmitter<ReplicationRule>();
|
||||
|
@ -20,7 +20,7 @@ export abstract class LabelService {
|
||||
|
||||
abstract getLabels(
|
||||
scope: string,
|
||||
projectId: number,
|
||||
projectId?: number,
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<Label[]> | Promise<Label[]>;
|
||||
@ -55,7 +55,7 @@ export class LabelDefaultService extends LabelService {
|
||||
|
||||
getLabels(
|
||||
scope: string,
|
||||
projectId: number,
|
||||
projectId?: number,
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): 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;
|
||||
this.inprogress = false;
|
||||
}).catch(err => {
|
||||
|
@ -49,7 +49,7 @@
|
||||
"bootstrap": "4.0.0-alpha.5",
|
||||
"codelyzer": "~2.0.0-beta.4",
|
||||
"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-spec-reporter": "2.5.0",
|
||||
"karma": "~1.7.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
|
||||
<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>
|
@ -17,6 +17,7 @@ import {Router, ActivatedRoute} from "@angular/router";
|
||||
import {ReplicationRule} from "harbor-ui";
|
||||
|
||||
import {SessionService} from "../../shared/session.service";
|
||||
import {AppConfigService} from "../../app-config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'total-replication',
|
||||
@ -26,6 +27,7 @@ export class TotalReplicationPageComponent {
|
||||
|
||||
constructor(private router: Router,
|
||||
private session: SessionService,
|
||||
private appConfigService: AppConfigService,
|
||||
private activeRoute: ActivatedRoute) {}
|
||||
customRedirect(rule: ReplicationRule): void {
|
||||
if (rule) {
|
||||
@ -40,4 +42,8 @@ export class TotalReplicationPageComponent {
|
||||
let account = this.session.getCurrentUser();
|
||||
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_FAILURE": "Failed to ping endpoint.",
|
||||
"NAME": "Name",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Project",
|
||||
"NAME_IS_REQUIRED": "Name is required.",
|
||||
"DESCRIPTION": "Description",
|
||||
@ -344,7 +345,10 @@
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"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."
|
||||
"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": {
|
||||
"NEW_ENDPOINT": "New Endpoint",
|
||||
|
@ -271,6 +271,7 @@
|
||||
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
|
||||
"TEST_CONNECTION_FAILURE": "Fallo al conectar con el endpoint.",
|
||||
"NAME": "Nombre",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Proyecto",
|
||||
"NAME_IS_REQUIRED": "El nombre es obligatorio.",
|
||||
"DESCRIPTION": "Descripción",
|
||||
@ -343,7 +344,10 @@
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"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."
|
||||
"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": {
|
||||
"NEW_ENDPOINT": "Nuevo Endpoint",
|
||||
|
@ -250,6 +250,7 @@
|
||||
"TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.",
|
||||
"TEST_CONNECTION_FAILURE": "Echec du ping du point final.",
|
||||
"NAME": "Nom",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Projet",
|
||||
"NAME_IS_REQUIRED": "Le nom est obligatoire.",
|
||||
"DESCRIPTION": "Description",
|
||||
@ -321,7 +322,10 @@
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"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."
|
||||
"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": {
|
||||
"NEW_ENDPOINT": "Nouveau Point Final",
|
||||
|
@ -271,6 +271,7 @@
|
||||
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
|
||||
"TEST_CONNECTION_FAILURE": "测试连接失败。",
|
||||
"NAME": "名称",
|
||||
"STATUS": "状态",
|
||||
"PROJECT": "项目",
|
||||
"NAME_IS_REQUIRED": "名称为必填项。",
|
||||
"DESCRIPTION": "描述",
|
||||
@ -343,7 +344,10 @@
|
||||
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。",
|
||||
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。",
|
||||
"NEW": "新增",
|
||||
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。"
|
||||
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
|
||||
"ACKNOWLEDGE": "确认",
|
||||
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "新建目标",
|
||||
|
Loading…
Reference in New Issue
Block a user