mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 17:47:46 +01:00
Merge pull request #8178 from jwangyangls/replicationAddLabelFilter
Add label filter in replication Ng
This commit is contained in:
commit
e87381d671
@ -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,6 +106,7 @@
|
||||
<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==='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>
|
||||
|
@ -269,3 +269,28 @@ 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;
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ export interface ReplicationRule extends Base {
|
||||
|
||||
export class Filter {
|
||||
type: string;
|
||||
value?: string;
|
||||
value?: any;
|
||||
constructor(type: string) {
|
||||
this.type = type;
|
||||
}
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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": {
|
||||
|
Loading…
Reference in New Issue
Block a user