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> <option *ngFor="let value of supportedFilters[i]?.values;" value="{{value}}">{{value}}</option>
</select> </select>
</div> </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"> <div class="resource-box" *ngIf="supportedFilters[i]?.style==='radio' && supportedFilters[i]?.values.length <= 1">
<span>{{supportedFilters[i]?.values}}</span> <span>{{supportedFilters[i]?.values}}</span>
</div> </div>
@ -85,7 +106,8 @@
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen> <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==='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==='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-content>
</clr-tooltip> </clr-tooltip>
</div> </div>

View File

@ -268,4 +268,29 @@ clr-modal {
.display-none{ .display-none{
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; cronString: string;
supportedTriggers: string[]; supportedTriggers: string[];
supportedFilters: Filter[]; supportedFilters: Filter[];
supportedFilterLabels: { name: string; color: string; select: boolean; scope: string; }[] = [];
@Input() withAdmiral: boolean; @Input() withAdmiral: boolean;
@Output() goToRegistry = new EventEmitter<any>(); @Output() goToRegistry = new EventEmitter<any>();
@ -92,6 +94,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.supportedFilters = adapter.supported_resource_filters; this.supportedFilters = adapter.supported_resource_filters;
this.supportedFilters.forEach(element => { this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type)); this.filters.push(this.initFilter(element.type));
// get supportedFilterLabels labels from supportedFilters
this.getLabelListFromAdapter(element);
}); });
this.supportedTriggers = adapter.supported_triggers; this.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]); this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]);
@ -261,15 +265,33 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
get filters(): FormArray { get filters(): FormArray {
console.log(this.ruleForm.get("filters"));
return this.ruleForm.get("filters") as FormArray; return this.ruleForm.get("filters") as FormArray;
} }
setFilter(filters: Filter[]) { 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); const filterFormArray = this.fb.array(filterFGs);
this.ruleForm.setControl("filters", filterFormArray); this.ruleForm.setControl("filters", filterFormArray);
} }
initFilter(name: string) { 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({ return this.fb.group({
type: name, type: name,
value: '' value: ''
@ -314,7 +336,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
let filters: any = copyRuleForm.filters; let filters: any = copyRuleForm.filters;
// remove the filters which user not set. // remove the filters which user not set.
for (let i = filters.length - 1; i >= 0; i--) { 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); copyRuleForm.filters.splice(i, 1);
} }
} }
@ -356,6 +379,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.inlineAlert.close(); this.inlineAlert.close();
this.noSelectedEndpoint = true; this.noSelectedEndpoint = true;
this.isRuleNameValid = true; this.isRuleNameValid = true;
this.supportedFilterLabels = [];
this.policyId = -1; this.policyId = -1;
this.createEditRuleOpened = true; this.createEditRuleOpened = true;
@ -373,7 +398,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.repService.getRegistryInfo(srcRegistryId) this.repService.getRegistryInfo(srcRegistryId)
.pipe(finalize(() => (this.onGoing = false))) .pipe(finalize(() => (this.onGoing = false)))
.subscribe(adapter => { .subscribe(adapter => {
this.setFilterAndTrigger(adapter); this.setFilterAndTrigger(adapter, ruleInfo);
this.updateRuleFormAndCopyUpdateForm(ruleInfo); this.updateRuleFormAndCopyUpdateForm(ruleInfo);
}, (error: any) => { }, (error: any) => {
this.inlineAlert.showInlineError(error); 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.supportedFilters = adapter.supported_resource_filters;
this.setFilter([]); this.setFilter([]);
this.supportedFilters.forEach(element => { this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type)); 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.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]); 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 { close(): void {
this.createEditRuleOpened = false; this.createEditRuleOpened = false;
} }
@ -482,4 +553,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
return trigger_settingsControls.controls.cron.touched || trigger_settingsControls.controls.cron.dirty; 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 { export class Filter {
type: string; type: string;
value?: string; value?: any;
constructor(type: string) { constructor(type: string) {
this.type = type; this.type = type;
} }

View File

@ -58,6 +58,7 @@
"TOOLTIP": { "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.", "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.", "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.", "RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.", "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.", "PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -58,6 +58,7 @@
"TOOLTIP": { "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.", "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.", "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.", "RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.", "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.", "PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -55,6 +55,7 @@
"TOOLTIP": { "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.", "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.", "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.", "RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.", "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.", "PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

@ -58,6 +58,7 @@
"TOOLTIP": { "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.", "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.", "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.", "RESOURCE_FILTER": "Filter the type of resources.",
"PUSH_BASED": "Push the resources from the local Harbor to the remote registry.", "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.", "PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",

View File

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