Support excluding rule for adding a replication rule (#15368)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
孙世军 2021-08-19 14:54:09 +08:00 committed by GitHub
parent f3a875abd7
commit 14c0a61d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 128 deletions

View File

@ -13,3 +13,8 @@ export const Flatten_I18n_MAP = {
[Flatten_Level.FLATTEN_LEVEl_3]: 'REPLICATION.FLATTEN_LEVEL_3',
[Flatten_Level.FLATTEN_ALL]: 'REPLICATION.FLATTEN_ALL',
};
export enum Decoration {
MATCHES = 'matches',
EXCLUDES = 'excludes'
}

View File

@ -7,7 +7,7 @@
<label class="clr-control-label required">{{'REPLICATION.NAME' | translate}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input class="clr-input" type="text" id="ruleName" size="35" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" required maxlength="255"
<input class="clr-input width-input" type="text" id="ruleName" size="35" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" required maxlength="255"
formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
<span class="spinner spinner-inline spinner-pos" [hidden]="!inNameChecking"></span>
@ -18,7 +18,7 @@
<!--Description-->
<clr-textarea-container>
<label>{{'REPLICATION.DESCRIPTION' | translate}}</label>
<textarea clrTextarea type="text" id="ruleDescription" class="inputWidth" row=3 formControlName="description"></textarea>
<textarea clrTextarea type="text" id="ruleDescription" class="width-input" row=3 formControlName="description"></textarea>
</clr-textarea-container>
<!-- replication mode -->
<clr-radio-container clrInline>
@ -53,7 +53,7 @@
<label class="required clr-control-label">{{'REPLICATION.SOURCE_REGISTRY' | translate}}</label>
<div class="clr-control-container">
<div class="clr-select-wrapper">
<select class="clr-select select-width" id="src_registry_id" (change)="sourceChange($event)" formControlName="src_registry"
<select class="clr-select width-input" id="src_registry_id" (change)="sourceChange($event)" formControlName="src_registry"
[compareWith]="equals">
<option class="display-none"></option>
<option *ngFor="let source of sourceList" [ngValue]="source">{{source.name}}-{{source.url}}</option>
@ -71,24 +71,41 @@
<span class="spinner spinner-inline spinner-position" [hidden]="onGoing === false"></span>
<div formArrayName="filters" class="clr-control-container">
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index">
<div [formGroupName]="i" >
<div class="width-70">
<label>{{"REPLICATION." + supportedFilters[i]?.type.toUpperCase() | translate}}:</label>
<div [formGroupName]="i" class="flex">
<label class="sub-label">{{"REPLICATION." + supportedFilters[i]?.type.toUpperCase() | translate}}:</label>
<div *ngIf="supportedFilters[i]?.style==='input'" class="flex">
<div class="clr-select-wrapper mr-1" *ngIf="supportedFilters[i]?.type==='tag'">
<select class="clr-select width-match-exclude" formControlName="decoration">
<option value="matches">{{'TAG_RETENTION.MAT' | translate}}</option>
<option value="excludes">{{'TAG_RETENTION.EXC' | translate}}</option>
</select>
</div>
<div class="clr-input-wrapper">
<input class="clr-input"
autocomplete="off"
[ngClass]="{
'width-name-resource': supportedFilters[i]?.type!=='tag',
'width-tag-label': supportedFilters[i]?.type==='tag'
}"
(input)="trimText($event)" type="text" #filterValue size="14" formControlName="value" id="{{'filter_'+ supportedFilters[i]?.type}}">
</div>
</div>
<label *ngIf="supportedFilters[i]?.style==='input'" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='(filter.value.dirty || filter.value.touched) && filter.value.invalid'>
<input class="clr-input" (input)="trimText($event)" type="text" #filterValue size="14" formControlName="value" id="{{'filter_'+ supportedFilters[i]?.type}}">
</label>
<div class="select resource-box clr-select-wrapper" *ngIf="supportedFilters[i]?.style==='radio' && supportedFilters[i]?.values.length > 1">
<select class="clr-select width-100" formControlName="value" #selectedValue id="{{'select_'+ supportedFilters[i]?.type}}"
<select class="clr-select width-name-resource" formControlName="value" #selectedValue id="{{'select_'+ supportedFilters[i]?.type}}"
name="{{supportedFilters[i]?.type}}">
<option value="">{{'REPLICATION.ALL' | translate}}</option>
<option *ngFor="let value of supportedFilters[i]?.values;" value="{{value}}">{{value}}{{value==='chart'? ' (chartmuseum)': ''}}</option>
</select>
</div>
<div class="select resource-box" *ngIf="supportedFilters[i]?.type==='label'&& supportedFilters[i]?.style==='list'">
<div class="dropdown width-100 clr-select-wrapper" formArrayName="value">
<clr-dropdown class="width-100">
<div class="select flex" *ngIf="supportedFilters[i]?.type==='label'&& supportedFilters[i]?.style==='list'">
<div class="clr-select-wrapper mr-1">
<select class="clr-select width-match-exclude" formControlName="decoration">
<option value="matches">{{'TAG_RETENTION.MAT' | translate}}</option>
<option value="excludes">{{'TAG_RETENTION.EXC' | translate}}</option>
</select>
</div>
<div class="dropdown clr-select-wrapper" formArrayName="value">
<clr-dropdown class="width-tag-label">
<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>
@ -99,12 +116,10 @@
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>
<clr-dropdown-menu class="right-align" clrPosition="bottom-left" *clrIfOpen>
<button type="button" class="dropdown-item flex" *ngFor="let value of supportedFilterLabels" (click)="stickLabel(value,i)">
<clr-icon shape="check" [style.visibility]="value.select ? 'visible' : 'hidden'"></clr-icon>
<hbr-label-piece [label]="value" [labelWidth]="130"></hbr-label-piece>
</button>
</clr-dropdown-menu>
</clr-dropdown>
@ -131,7 +146,7 @@
<label class="clr-control-label required">{{'REPLICATION.DEST_REGISTRY' | translate}}</label>
<div class="form-select clr-control-container">
<div class="clr-select-wrapper">
<select class="clr-select select-width" id="dest_registry" (change)="targetChange($event)" formControlName="dest_registry"
<select class="clr-select width-input" id="dest_registry" (change)="targetChange($event)" formControlName="dest_registry"
[compareWith]="equals">
<option class="display-none"></option>
<option *ngFor="let target of targetList" [ngValue]="target">{{target.name}}-{{target.url}}</option>
@ -147,9 +162,9 @@
<div class="clr-form-control">
<label for="dest_namespace" class="clr-control-label">{{'REPLICATION.DESTINATION' | translate}}</label>
<div class="clr-control-container" [class.clr-error]="(ruleForm.controls.dest_namespace.dirty || ruleForm.controls.dest_namespace.touched) && ruleForm.controls.dest_namespace.invalid">
<div class="clr-input-wrapper">
<div class="clr-input-wrapper flex">
<label class="sub-label">{{'REPLICATION.NAMESPACE' | translate}}:</label>
<input autocomplete="off" class="clr-input" formControlName="dest_namespace" type="text" id="dest_namespace" pattern="^[a-z0-9]+(?:[/._-][a-z0-9]+)*$"
<input autocomplete="off" class="clr-input width-name-resource" formControlName="dest_namespace" type="text" id="dest_namespace" pattern="^[a-z0-9]+(?:[/._-][a-z0-9]+)*$"
maxlength="255">
<clr-tooltip class="des-tooltip">
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
@ -167,28 +182,24 @@
<div class="clr-form-control">
<label for="dest_namespace_replace_count" class="clr-control-label"></label>
<div class="clr-control-container">
<div class="input-width flex">
<div class="clr-select-wrapper">
<label class="sub-label">{{'REPLICATION.REPO_FLATTENING' | translate}}:</label>
<select [attr.disabled]="(ruleForm.controls.dest_namespace.invalid || !ruleForm.controls.dest_namespace.value) ? 'disabled' : null" id="dest_namespace_replace_count" formControlName="dest_namespace_replace_count" class="clr-select count-type">
<option *ngFor="let item of flattenLevelMap | keyvalue"
[value]="item.key">{{item.value | translate}}</option>
</select>
</div>
<div class="clr-input-wrapper">
<clr-tooltip class="des-tooltip">
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<clr-tooltip-content class="flatten" clrPosition="top-left" clrSize="lg" *clrIfOpen>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_ALL' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_NO' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_1' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_2' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_3' | translate}}</div>
<div>{{'REPLICATION.NOTE' | translate}}</div>
</clr-tooltip-content>
</clr-tooltip>
</div>
<div class="clr-select-wrapper flex">
<label class="sub-label">{{'REPLICATION.REPO_FLATTENING' | translate}}:</label>
<select [attr.disabled]="(ruleForm.controls.dest_namespace.invalid || !ruleForm.controls.dest_namespace.value) ? 'disabled' : null" id="dest_namespace_replace_count" formControlName="dest_namespace_replace_count" class="clr-select width-name-resource">
<option *ngFor="let item of flattenLevelMap | keyvalue"
[value]="item.key">{{item.value | translate}}</option>
</select>
<clr-tooltip class="des-tooltip">
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<clr-tooltip-content class="flatten" clrPosition="top-left" clrSize="lg" *clrIfOpen>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_ALL' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_NO' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_1' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_2' | translate}}</div>
<div>{{'REPLICATION.FLATTEN_LEVEL_TIP_3' | translate}}</div>
<div>{{'REPLICATION.NOTE' | translate}}</div>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
</div>
@ -198,8 +209,8 @@
<div class="clr-control-container">
<div formGroupName="trigger">
<!--on trigger-->
<div class="select width-115 clr-select-wrapper">
<select (change)="changeTrigger($event)" id="ruleTrigger" formControlName="type" class="clr-select">
<div class="select clr-select-wrapper">
<select (change)="changeTrigger($event)" id="ruleTrigger" formControlName="type" class="clr-select width-input">
<option *ngFor="let trigger of supportedTriggers" [value]="trigger">{{'REPLICATION.' + trigger.toUpperCase() | translate }}</option>
</select>
</div>
@ -223,7 +234,7 @@
</div>
</div>
</div>
<div class="clr-checkbox-wrapper clr-form-control" [hidden]="isNotEventBased()">
<div class="clr-checkbox-wrapper clr-form-control mt-0" [hidden]="isNotEventBased()">
<input type="checkbox" class="clr-checkbox" [checked]="false" id="ruleDeletion" formControlName="deletion">
<label for="ruleDeletion">{{'REPLICATION.DELETE_REMOTE_IMAGES' | translate}}</label>
</div>

View File

@ -19,10 +19,6 @@ h4 {
font-size: 12px;
}
.inputWidth {
width: 270px;
}
.endpointSelect {
width: 270px;
margin-right: 10px;
@ -38,34 +34,10 @@ h4 {
margin-left: 10px;
}
.filterSelect label {
width: 190px;
}
.filterSelect label input {
width: 100%;
}
.pull-left {
float: left;
}
.padLeft0 {
padding-left: 0;
}
.width-70 {
display: inline-block;
width: 70px;
margin-right: 10px;
}
.width-115 {
display: inline-block;
width: 115px;
margin-right: 10px;
}
.schedule-style {
display: inline-block;
}
@ -164,7 +136,7 @@ h4 {
clr-modal {
::ng-deep div.modal-dialog {
width: 29rem;
width: 32rem;
}
}
@ -200,25 +172,6 @@ clr-modal {
width: 100%;
}
.loading-center {
display: block;
text-align: center;
}
.width-315 {
display: flex;
align-items: center;
width:315px;
}
.mr-t-10 {
margin-top: 10px;
}
.resource-box {
display: inline-block;
width: 190px;
}
.form-cron {
padding-left:3.8rem;
}
@ -266,9 +219,6 @@ clr-modal {
outline: none;
border-bottom: 1px solid rgb(154, 154, 154);
}
.labelDiv {
padding-left: 26px;
}
.ellipsis {
margin-left: 0.2rem;
font-size: 16px;
@ -280,30 +230,40 @@ clr-modal {
justify-content: space-between;
}
.select-width {
min-width:11rem;
}
.flex {
display: flex;
}
.sub-label {
display: inline-block;
width: 100px;
margin-right: 10px;
flex: 0 0 5rem;
}
.clr-control-label {
width: 8.6rem;
}
.count {
width: 34px;
margin-left: 10px;
}
.des-tooltip {
margin-left: 10px;
}
.count-type {
width: 158px;
margin-left: 0.5rem;
}
:host::ng-deep.flatten {
width: 23rem!important;
}
.width-input {
width: 17rem;
}
.width-name-resource {
flex: 0 0 12rem;
width: 12rem;
}
.width-match-exclude {
width: 5rem;
}
.width-tag-label {
width: 6rem;
}
.right-align {
transform: translateX(-4.3rem) translateY(1.25rem)!important;
}

View File

@ -35,7 +35,7 @@ import { RegistryService } from "../../../../../../../ng-swagger-gen/services/re
import { Registry } from "../../../../../../../ng-swagger-gen/models/registry";
import { Label } from "../../../../../../../ng-swagger-gen/models/label";
import { LabelService } from "../../../../../../../ng-swagger-gen/services/label.service";
import { Flatten_I18n_MAP, Flatten_Level } from "../../replication";
import { Decoration, Flatten_I18n_MAP, Flatten_Level } from "../../replication";
const PREFIX: string = '0 ';
const PAGE_SIZE: number = 100;
@ -98,12 +98,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.repService.getRegistryInfo(id)
.pipe(finalize(() => (this.onGoing = false)))
.subscribe(adapter => {
this.supportedFilters = adapter.supported_resource_filters;
this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type));
});
this.supportedTriggers = adapter.supported_triggers;
this.ruleForm.get("trigger").get("type").setValue(this.supportedTriggers[0]);
this.setFilterAndTrigger(adapter);
}, (error: any) => {
this.inlineAlert.showInlineError(error);
});
@ -315,14 +310,21 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
const filterFGs = filters.map(filter => {
if (filter.type === FilterType.LABEL) {
let fbLabel = this.fb.group({
type: FilterType.LABEL
type: FilterType.LABEL,
decoration: filter.decoration || Decoration.MATCHES
});
let filterLabel = this.fb.array(filter.value);
fbLabel.setControl('value', filterLabel);
return fbLabel;
} else {
return this.fb.group(filter);
}
if (filter.type === FilterType.TAG) {
return this.fb.group({
type: FilterType.TAG,
decoration: filter.decoration || Decoration.MATCHES,
value: filter.value
});
}
return this.fb.group(filter);
});
const filterFormArray = this.fb.array(filterFGs);
this.ruleForm.setControl("filters", filterFormArray);
@ -331,10 +333,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
initFilter(name: string) {
if (name === FilterType.LABEL) {
const labelArray = this.fb.array([]);
const labelControl = this.fb.group({type: name});
const labelControl = this.fb.group({
type: name,
decoration: Decoration.MATCHES
});
labelControl.setControl('value', labelArray);
return labelControl;
}
if (name === FilterType.TAG) {
return this.fb.group({
type: name,
decoration: Decoration.MATCHES,
value: ''
});
}
return this.fb.group({
type: name,
value: ''
@ -381,7 +393,7 @@ 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 === "" || (filters[i].value instanceof Array
if (!filters[i].value || (filters[i].value instanceof Array
&& filters[i].value.length === 0)) {
copyRuleForm.filters.splice(i, 1);
}
@ -455,7 +467,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.repService.getRegistryInfo(srcRegistryId)
.pipe(finalize(() => (this.onGoing = false)))
.subscribe(adapter => {
this.setFilterAndTrigger(adapter, ruleInfo);
this.setFilterAndTrigger(adapter);
this.updateRuleFormAndCopyUpdateForm(ruleInfo);
}, (error: any) => {
this.inlineAlert.showInlineError(error);
@ -471,6 +483,11 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
.subscribe(adapter => {
this.setFilterAndTrigger(adapter);
this.copyUpdateForm = clone(this.ruleForm.value);
if (this.supportedFilterLabels && this.supportedFilterLabels.length) {
this.supportedFilterLabels.forEach((label, index) => {
label.select = false;
});
}
}, (error: any) => {
this.inlineAlert.showInlineError(error);
});
@ -479,9 +496,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
}
setFilterAndTrigger(adapter, ruleInfo?) {
setFilterAndTrigger(adapter) {
this.supportedFilters = adapter.supported_resource_filters;
this.setFilter([]);
this.supportedFilters.forEach(element => {
this.filters.push(this.initFilter(element.type));
});

View File

@ -49,6 +49,7 @@ export interface PingEndpoint extends Base {
export interface Filter {
type: string;
style: string;
decoration?: string;
values?: string[];
}