Add label filter in replication Ng

Signed-off-by: Yogi_Wang <yawang@vmware.com>
This commit is contained in:
Yogi_Wang 2019-07-01 14:54:12 +08:00
parent 6d2d5a6a2a
commit 9c07caa1a6
9 changed files with 154 additions and 15 deletions

View File

@ -77,6 +77,27 @@
<option *ngFor="let value of supportedFilters[i]?.values;" value="{{value}}">{{value}}</option>
</select>
</div>
<div class="select resource-box" *ngIf="supportedFilters[i]?.type==='label'&& supportedFilters[i]?.style==='list'">
<div class="dropdown width-100" formArrayName="value">
<clr-dropdown class="width-100">
<button type="button" class="width-100 dropdown-toggle btn btn-link statistic-data label-text" clrDropdownTrigger>
<ng-template ngFor let-label [ngForOf]="filter.value.value" let-m="index">
<span class="label" *ngIf="m<1"> {{label}} </span>
</ng-template>
<span class="ellipsis" *ngIf="filter.value.value.length>1">···</span>
<div *ngFor="let label1 of filter.value.value;let k = index" hidden="true">
<input type="text" [formControlName]="k" #labelValue id="{{'label_'+ supportedFilters[i]?.type + '_' + label1}}" name="{{'label_'+ supportedFilters[i]?.type + '_' + label1}}" placeholder="select labels" >
</div>
</button>
<clr-dropdown-menu class="width-100" clrPosition="bottom-left" *clrIfOpen>
<button type="button" class="dropdown-item" *ngFor="let value of supportedFilterLabels" (click)="stickLabel(value,i)">
<clr-icon shape="check" [hidden]="!value.select" class='pull-left'></clr-icon>
<div class='labelDiv'><hbr-label-piece [label]="value" [labelWidth]="130"></hbr-label-piece></div>
</button>
</clr-dropdown-menu>
</clr-dropdown>
</div>
</div>
<div class="resource-box" *ngIf="supportedFilters[i]?.style==='radio' && supportedFilters[i]?.values.length <= 1">
<span>{{supportedFilters[i]?.values}}</span>
</div>
@ -85,7 +106,8 @@
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='name'">{{'TOOLTIP.NAME_FILTER' | translate}}</span>
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='tag'">{{'TOOLTIP.TAG_FILTER' | translate}}</span>
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='resource'">{{'TOOLTIP.RESOURCE_FILTER' | translate}}</span>
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='label'">{{'TOOLTIP.LABEL_FILTER' | translate}}</span>
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='resource'">{{'TOOLTIP.RESOURCE_FILTER' | translate}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>

View File

@ -268,4 +268,29 @@ clr-modal {
.display-none{
display: none;
}
.width-100 {
width: 100%;
}
.label-text {
text-transform: none;
letter-spacing: normal;
font-size: 13px;
font-weight: 400;
color: #000;
height: 1.2rem;
margin: 0 !important;
line-height: 1rem;
text-align: left;
padding-left: 6px;
outline: none;
border-bottom: 1px solid rgb(154, 154, 154);
}
.labelDiv {
padding-left: 26px;
}
.ellipsis {
margin-left: 0.2rem;
font-size: 16px;
font-weight: 700;
}

View File

@ -67,6 +67,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
cronString: string;
supportedTriggers: string[];
supportedFilters: Filter[];
supportedFilterLabels: { name: string; color: string; select: boolean; scope: string; }[] = [];
@Input() withAdmiral: boolean;
@Output() goToRegistry = new EventEmitter<any>();
@ -92,6 +94,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.supportedFilters = adapter.supported_resource_filters;
this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type));
// get supportedFilterLabels labels from supportedFilters
this.getLabelListFromAdapter(element);
});
this.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]);
@ -261,15 +265,33 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
get filters(): FormArray {
console.log(this.ruleForm.get("filters"));
return this.ruleForm.get("filters") as FormArray;
}
setFilter(filters: Filter[]) {
const filterFGs = filters.map(filter => this.fb.group(filter));
const filterFGs = filters.map(filter => {
if (filter.type === 'label') {
let fbLabel = this.fb.group({
type: 'label'
});
let filterLabel = this.fb.array(filter.value);
fbLabel.setControl('value', filterLabel);
return fbLabel;
} else {
return this.fb.group(filter);
}
});
const filterFormArray = this.fb.array(filterFGs);
this.ruleForm.setControl("filters", filterFormArray);
}
initFilter(name: string) {
if (name === 'label') {
const labelArray = this.fb.array([]);
const labelControl = this.fb.group({type: name});
labelControl.setControl('value', labelArray);
return labelControl;
}
return this.fb.group({
type: name,
value: ''
@ -314,7 +336,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
let filters: any = copyRuleForm.filters;
// remove the filters which user not set.
for (let i = filters.length - 1; i >= 0; i--) {
if (filters[i].value === "") {
if (filters[i].value === "" || (filters[i].value instanceof Array
&& filters[i].value.length === 0)) {
copyRuleForm.filters.splice(i, 1);
}
}
@ -356,6 +379,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.inlineAlert.close();
this.noSelectedEndpoint = true;
this.isRuleNameValid = true;
this.supportedFilterLabels = [];
this.policyId = -1;
this.createEditRuleOpened = true;
@ -373,7 +398,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.repService.getRegistryInfo(srcRegistryId)
.pipe(finalize(() => (this.onGoing = false)))
.subscribe(adapter => {
this.setFilterAndTrigger(adapter);
this.setFilterAndTrigger(adapter, ruleInfo);
this.updateRuleFormAndCopyUpdateForm(ruleInfo);
}, (error: any) => {
this.inlineAlert.showInlineError(error);
@ -397,17 +422,63 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
}
setFilterAndTrigger(adapter) {
setFilterAndTrigger(adapter, ruleInfo?) {
this.supportedFilters = adapter.supported_resource_filters;
this.setFilter([]);
this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type));
// get supportedFilterLabels labels from supportedFilters
this.getLabelListFromAdapter(element);
// only when edit replication rule
if (ruleInfo && this.supportedFilterLabels.length) {
this.getLabelListFromRuleInfo(ruleInfo);
}
});
this.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]);
}
getLabelListFromAdapter(supportedFilter) {
if (supportedFilter.type === 'label') {
this.supportedFilterLabels = [];
supportedFilter.values.forEach( value => {
this.supportedFilterLabels.push({
name: value,
color: '#fff',
select: false,
scope: 'g'
});
});
}
}
getLabelListFromRuleInfo(ruleInfo) {
let labelValueObj = ruleInfo.filters.find((currentValue) => {
return currentValue.type === 'label';
});
if (labelValueObj) {
for (const labelValue of labelValueObj.value) {
let flagLabel = this.supportedFilterLabels.every((currentValue) => {
return currentValue.name !== labelValue;
});
if (flagLabel) {
this.supportedFilterLabels = [
{
name: labelValue,
color: '#fff',
select: true,
scope: 'g'
}, ...this.supportedFilterLabels];
}
//
for (const labelObj of this.supportedFilterLabels) {
if (labelObj.name === labelValue) {
labelObj.select = true;
}
}
}
}
}
close(): void {
this.createEditRuleOpened = false;
}
@ -482,4 +553,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
return trigger_settingsControls.controls.cron.touched || trigger_settingsControls.controls.cron.dirty;
}
stickLabel(value, index) {
value.select = !value.select;
let filters = this.ruleForm.get('filters') as FormArray;
let fromIndex = filters.controls[index] as FormGroup;
let labelValue = this.supportedFilterLabels.reduce( (cumulatedSelectedArrs, currentValue) => {
if (currentValue.select) {
if (!cumulatedSelectedArrs.length) {
return [currentValue.name];
}
return [...cumulatedSelectedArrs, currentValue.name];
}
return cumulatedSelectedArrs;
}, []);
fromIndex.setControl('value', this.fb.array(labelValue));
}
}

View File

@ -130,7 +130,7 @@ export interface ReplicationRule extends Base {
export class Filter {
type: string;
value?: string;
value?: any;
constructor(type: string) {
this.type = type;
}

View File

@ -58,6 +58,7 @@
"TOOLTIP": {
"NAME_FILTER": "Filter the name of the resource. Leave empty or use '**' to match all. 'library/**' only matches resources under 'library'. For more patterns, please refer to the user guide.",
"TAG_FILTER": "Filter the tag/version part of the resources. Leave empty or use '**' to match all. '1.0*' only matches the tags that starts with '1.0'. For more patterns, please refer to the user guide.",
"LABEL_FILTER": "Filter the resources according to labels.",
"RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.",
"PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -58,6 +58,7 @@
"TOOLTIP": {
"NAME_FILTER": "Filter the name of the resource. Leave empty or use '**' to match all. 'library/**' only matches resources under 'library'. For more patterns, please refer to the user guide.",
"TAG_FILTER": "Filter the tag/version part of the resources. Leave empty or use '**' to match all. '1.0*' only matches the tags that starts with '1.0'. For more patterns, please refer to the user guide.",
"LABEL_FILTER": "Filter the resources according to labels.",
"RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.",
"PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -55,6 +55,7 @@
"TOOLTIP": {
"NAME_FILTER": "Filter the name of the resource. Leave empty or use '**' to match all. 'library/**' only matches resources under 'library'. For more patterns, please refer to the user guide.",
"TAG_FILTER": "Filter the tag/version part of the resources. Leave empty or use '**' to match all. '1.0*' only matches the tags that starts with '1.0'. For more patterns, please refer to the user guide.",
"LABEL_FILTER": "Filter the resources according to labels.",
"RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.",
"PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -58,6 +58,7 @@
"TOOLTIP": {
"NAME_FILTER": "Filter the name of the resource. Leave empty or use '**' to match all. 'library/**' only matches resources under 'library'. For more patterns, please refer to the user guide.",
"TAG_FILTER": "Filter the tag/version part of the resources. Leave empty or use '**' to match all. '1.0*' only matches the tags that starts with '1.0'. For more patterns, please refer to the user guide.",
"LABEL_FILTER": "Filter the resources according to labels.",
"RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.",
"PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -58,6 +58,7 @@
"TOOLTIP": {
"NAME_FILTER": "过滤资源的名字。不填或者“”匹配所有资源“library/”只匹配“library”下的资源。更多的匹配模式请参考用户手册。",
"TAG_FILTER": "过滤资源的tag/version。不填或者“”匹配所有“1.0*”只匹配以“1.0”开头的tag/version。",
"LABEL_FILTER": "根据标签筛选资源。",
"RESOURCE_FILTER": "过滤资源的类型。",
"PUSH_BASED": "把资源由本地Harbor推送到远端仓库。",
"PULL_BASED": "把资源由远端仓库拉取到本地Harbor。",
@ -86,8 +87,8 @@
"NONEMPTY": "不能为空",
"ENDPOINT_FORMAT": "Endpoint必须以http://或https://开头。",
"OIDC_ENDPOIT_FORMAT": "Endpoint必须以https://开头。",
"OIDC_NAME": "OIDC提供商的名称.",
"OIDC_ENDPOINT": "OIDC服务器的地址.",
"OIDC_NAME": "OIDC提供商的名称",
"OIDC_ENDPOINT": "OIDC服务器的地址",
"OIDC_SCOPE": "在身份验证期间发送到OIDC服务器的scope。它必须包含“openid”和“offline_access”。如果您使用Google请从此字段中删除“脱机访问”。",
"OIDC_VERIFYCERT": "如果您的OIDC服务器是通过自签名证书托管的请取消选中此框。"
},
@ -298,7 +299,7 @@
"NEW_ROBOT_ACCOUNT": "添加机器人账户",
"ENABLED_STATE": "启用状态",
"EXPIRATION": "过期时间",
"NUMBER_REQUIRED":"此项为必填项且为不为0的整数.",
"NUMBER_REQUIRED":"此项为必填项且为不为0的整数",
"TOKEN_EXPIRATION":"机器人账户令牌过期时间(天)",
"DESCRIPTION": "描述",
"ACTION": "操作",
@ -314,10 +315,10 @@
"PUSH": "推送",
"PULL": "拉取",
"FILTER_PLACEHOLDER": "过滤机器人账户",
"ROBOT_NAME": "不能包含特殊字符(~#$%)且长度不能超过255.",
"ACCOUNT_EXISTING": "机器人账户已经存在.",
"ROBOT_NAME": "不能包含特殊字符(~#$%)且长度不能超过255",
"ACCOUNT_EXISTING": "机器人账户已经存在",
"ALERT_TEXT": "这是唯一一次复制您的个人访问令牌的机会",
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功.",
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功",
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
"DELETION_TITLE": "删除账户确认",
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?"
@ -727,7 +728,7 @@
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间分钟默认为30分钟。",
"ROBOT_TOKEN_EXPIRATION": "机器人账户的令牌的过期时间默认为30天,显示的结果为分钟转化的天数并向下取整。",
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。",
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书.",
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书",
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。",
"VERIFY_CERT": "检查来自LDAP服务端的证书",
"READONLY_TOOLTIP": "选中,表示正在维护状态,不可删除仓库及标签,也不可以推送镜像。",
@ -859,8 +860,8 @@
},
"CHART": {
"SCANNING_TIME": "扫描完成时间:",
"TOOLTIPS_TITLE": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}.",
"TOOLTIPS_TITLE_SINGULAR": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}.",
"TOOLTIPS_TITLE": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}",
"TOOLTIPS_TITLE_SINGULAR": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}",
"TOOLTIPS_TITLE_ZERO": "没有发现可识别的漏洞包"
},
"SEVERITY": {