diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.scss b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.scss
index 416fa483a..98326fad0 100644
--- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.scss
+++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.scss
@@ -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;
+}
+
diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts
index 359247281..c216fef58 100644
--- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts
+++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts
@@ -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 }
]
});
}));
diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts
index 35d3c9b2b..67cec6ed5 100644
--- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts
+++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.ts
@@ -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
= new Subject();
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();
@Output() reload = new EventEmitter();
@@ -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(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(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 = 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 = 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 = this.ruleForm.controls["filters"];
+ const control = 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 = 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;
}
diff --git a/src/ui_ng/lib/src/create-edit-rule/filter-label.component.html b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.html
new file mode 100644
index 000000000..a28f37924
--- /dev/null
+++ b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.html
@@ -0,0 +1,13 @@
+
+
×
+
+
+
{{'LABEL.NO_LABELS' | translate }}
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/lib/src/create-edit-rule/filter-label.component.scss b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.scss
new file mode 100644
index 000000000..bf43a8b8c
--- /dev/null
+++ b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.scss
@@ -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;}
diff --git a/src/ui_ng/lib/src/create-edit-rule/filter-label.component.ts b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.ts
new file mode 100644
index 000000000..21c006982
--- /dev/null
+++ b/src/ui_ng/lib/src/create-edit-rule/filter-label.component.ts
@@ -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 = new Subject ();
+ @Input() isOpen: boolean;
+ @Input() projectId: number;
+ @Input() selectedLabelInfo: Label[];
+ @Output() selectedLabels = new EventEmitter();
+ @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