Add acceleration rules UI

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
AllForNothing 2022-04-08 19:14:59 +08:00
parent 8fa1e32c89
commit 955a857da0
8 changed files with 492 additions and 1 deletions

View File

@ -0,0 +1,68 @@
<clr-modal [(clrModalOpen)]="addRuleOpened" [clrModalStaticBackdrop]="true" [clrModalClosable]="true" [clrModalSize]="'lg'">
<h3 class="modal-title" *ngIf="isAdd">Add Tag Acceleration Rule</h3>
<h3 class="modal-title" *ngIf="!isAdd">Edit Tag Acceleration Rule</h3>
<div class="modal-body no-scrolling">
<inline-alert class="modal-title"></inline-alert>
<p class="color-97">Specify a tag acceleration rule for this project. Images that meet the rules will be
automatically accelerated when they are pushed to the project registry.</p>
<div class="height-72">
<div class="clr-row mt-1">
<div class="clr-col-4">
<span>{{'IMMUTABLE_TAG.IN_REPOSITORIES' | translate}}</span>
</div>
<div class="clr-col-3">
<div class="clr-select-wrapper w-100">
<select id="scope_selectors" [(ngModel)]="repoSelect" class="clr-select w-100">
<option value="repoMatches">matching</option>
<option value="repoExcludes">excluding</option>
</select>
</div>
</div>
<div class="clr-col-5">
<div class="w-100">
<input required id="scope-input" [(ngModel)]="repositories" class="clr-input w-100">
</div>
</div>
</div>
<div class="clr-row">
<div class="clr-col-4"></div>
<div class="clr-col-8">
<span>{{'IMMUTABLE_TAG.REP_SEPARATOR' | translate}}</span>
</div>
</div>
</div>
<div class="height-72">
<div class="clr-row">
<div class="clr-col-4">
<label>{{'IMMUTABLE_TAG.TAGS' | translate}}</label>
</div>
<div class="clr-col-3">
<div class="clr-select-wrapper w-100">
<select id="tag_selectors" [(ngModel)]="tagsSelect" class="clr-select w-100">
<option value="matches">matching</option>
<option value="excludes">excluding</option>
</select>
</div>
</div>
<div class="clr-col-5">
<div class="w-100">
<input required id="tag-input" [(ngModel)]="tagsInput" class="clr-input w-100">
</div>
</div>
</div>
<div class="clr-row">
<div class="clr-col-4"></div>
<div class="clr-col-8">
<span>{{'IMMUTABLE_TAG.TAG_SEPARATOR' | translate}}</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" id="close-btn" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button [disabled]="canNotAdd()" type="button" id="add-edit-btn" class="btn btn-primary" (click)="add()">
<span *ngIf="isAdd">{{'BUTTON.ADD' | translate}}</span>
<span *ngIf="!isAdd">{{'BUTTON.SAVE' | translate}}</span>
</button>
</div>
</clr-modal>

View File

@ -0,0 +1,7 @@
.color-97 {
color: #979797;
}
.height-72 {
height: 72px;
}

View File

@ -0,0 +1,150 @@
import {
Component,
OnInit,
OnDestroy,
Output,
EventEmitter, ViewChild, Input,
} from "@angular/core";
import { ImmutableRetentionRule, RuleMetadate } from "../../tag-retention/retention";
import { InlineAlertComponent } from "../../../../../shared/components/inline-alert/inline-alert.component";
import { ImmutableTagService } from "../../immutable-tag/immutable-tag.service";
const EXISTING_RULE = "TAG_RETENTION.EXISTING_RULE";
const INVALID_RULE = "TAG_RETENTION.INVALID_RULE";
@Component({
selector: 'app-add-acceleration-rule',
templateUrl: './add-acceleration-rule.component.html',
styleUrls: ['./add-acceleration-rule.component.scss']
})
export class AddAccelerationRuleComponent implements OnInit, OnDestroy {
addRuleOpened: boolean = false;
@Output() clickAdd = new EventEmitter<{
rule: ImmutableRetentionRule;
isAdd: boolean
}>();
@Input() rules: ImmutableRetentionRule[];
@Input() projectId: number;
metadata: RuleMetadate = new RuleMetadate();
isAdd: boolean = true;
editRuleOrigin: ImmutableRetentionRule;
onGoing: boolean = false;
@ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
repoSelect: string = 'repoMatches';
repositories: string = '**';
tagsSelect: string = 'matches';
tagsInput: string = "**";
repoSelectEditOrigin: string;
repositoriesEditOrigin: string;
tagsSelectEditOrigin: string;
tagsInputEditOrigin: string;
constructor(private immutableTagService: ImmutableTagService) {
}
ngOnInit(): void {
}
ngOnDestroy(): void {
}
canNotAdd(): boolean {
if (this.onGoing) {
return true;
}
if (!this.repositories) {
return true;
}
if (!this.tagsInput) {
return true;
}
// tslint:disable-next-line:triple-equals
if (!this.isAdd && this.repoSelect != this.repoSelectEditOrigin) {
return false;
}
// tslint:disable-next-line:triple-equals
if (!this.isAdd && this.repositories != this.repositoriesEditOrigin) {
return false;
}
// tslint:disable-next-line:triple-equals
if (!this.isAdd && this.tagsSelect != this.tagsSelectEditOrigin) {
return false;
}
// tslint:disable-next-line:triple-equals
if (!this.isAdd && this.tagsInput != this.tagsInputEditOrigin) {
return false;
}
return !this.isAdd;
}
open() {
this.addRuleOpened = true;
this.inlineAlert.alertClose = true;
this.onGoing = false;
}
close() {
this.addRuleOpened = false;
}
cancel() {
this.close();
}
add() {
if (this.isExistingRule()) {
this.inlineAlert.showInlineError(EXISTING_RULE);
return;
}
// remove whitespaces
const rule: ImmutableRetentionRule = new ImmutableRetentionRule(this.projectId);
rule.scope_selectors.repository[0].pattern = this.repositories;
rule.scope_selectors.repository[0].decoration = this.repoSelect;
rule.tag_selectors[0].pattern = this.tagsInput;
rule.tag_selectors[0].decoration = this.tagsSelect;
rule.scope_selectors.repository[0].pattern = rule.scope_selectors.repository[0].pattern.replace(/\s+/g, "");
rule.tag_selectors[0].pattern = rule.tag_selectors[0].pattern.replace(/\s+/g, "");
if (rule.scope_selectors.repository[0].decoration !== "repoMatches"
&& rule.scope_selectors.repository[0].pattern) {
let str = rule.scope_selectors.repository[0].pattern;
str = str.replace(/[{}]/g, "");
const arr = str.split(',');
for (let i = 0; i < arr.length; i++) {
if (arr[i] && arr[i].trim() && arr[i] === "**") {
this.inlineAlert.showInlineError(INVALID_RULE);
return;
}
}
}
this.clickAdd.emit({
rule: rule,
isAdd: this.isAdd
});
}
isExistingRule(): boolean {
if (this.rules && this.rules.length > 0) {
for (let i = 0; i < this.rules.length; i++) {
if (this.isSameRule(this.rules[i])) {
return true;
}
}
}
return false;
}
isSameRule(rule: ImmutableRetentionRule): boolean {
if (this.repoSelect !== rule.scope_selectors.repository[0].decoration) {
return false;
}
if (this.repositories !== rule.scope_selectors.repository[0].pattern) {
return false;
}
if (this.tagsSelect !== rule.tag_selectors[0].decoration) {
return false;
}
return this.tagsInput === rule.tag_selectors[0].pattern;
}
getI18nKey(str: string) {
return this.immutableTagService.getI18nKey(str);
}
}

View File

@ -0,0 +1,60 @@
<div class="clr-row pt-1 fw8">
<div class="clr-col">
<label class="label-left font-size-54">Acceleration rules</label><span class="badge badge-3 ml-5">{{rules?.length ? rules?.length : 0}}/15</span>
<span *ngIf="loadingRule" class="spinner spinner-inline ml-2">Loading...</span>
</div>
</div>
<div class="clr-row pt-1">
<div class="clr-col">
<ul *ngIf="rules?.length > 0" class="list-unstyled">
<li class="rule" *ngFor="let rule of rules;let i = index;">
<div class="clr-row">
<div class="clr-col-2 flex-150">
<div class="dropdown" [ngClass]="{open:ruleIndex===i}">
<button (click)="openEditor(i)" id="{{'action'+i}}" class="dropdown-toggle btn btn-sm btn-link ">
{{'IMMUTABLE_TAG.ACTION' | translate}}
<clr-icon shape="caret down"></clr-icon>
</button>
<div class="dropdown-menu">
<button *ngIf="!rule?.disabled" type="button" id="{{'disable-btn'+i}}" class="dropdown-item" (click)="toggleDisable(i)">{{'IMMUTABLE_TAG.DISABLE' | translate}}</button>
<button *ngIf="rule?.disabled" type="button" class="dropdown-item" (click)="toggleDisable(i)">{{'IMMUTABLE_TAG.ENABLE' | translate}}</button>
<button type="button" id="{{'edit-btn'+i}}" class="dropdown-item" (click)="editRuleByIndex(i)">{{'IMMUTABLE_TAG.EDIT' | translate}}</button>
<button type="button" class="dropdown-item" id="{{'delete-btn'+i}}" (click)="deleteRule(i)">{{'IMMUTABLE_TAG.DELETE' | translate}}</button>
</div>
</div>
</div>
<div class="clr-col">
<span>
<clr-icon *ngIf="!rule?.disabled" class="color-green color-white-dark" shape="success-standard"></clr-icon>
<clr-icon id="{{'disable-icon'+i}}" *ngIf="rule?.disabled" class="color-red" shape="error-standard"></clr-icon>
</span>
<span class="rule-name ml-5">
<span>{{'IMMUTABLE_TAG.IN_REPOSITORIES' | translate}}</span>
<span>{{getI18nKey(rule?.scope_selectors?.repository[0]?.decoration)|translate}}</span>
<span>{{formatPattern(rule?.scope_selectors?.repository[0]?.pattern)}}</span>
<span>,</span>
<span>{{'IMMUTABLE_TAG.LOWER_TAGS' | translate}}</span>
<span>{{getI18nKey(rule?.tag_selectors[0]?.decoration)|translate}}</span>
<span id="{{'tag-selectors-patten'+i}}">{{formatPattern(rule?.tag_selectors[0]?.pattern)}}</span>
<ng-container *ngIf="rule?.tag_selectors[1]?.pattern && rule?.tag_selectors[1]?.pattern">
<span class="color-97">{{'IMMUTABLE_TAG.AND' | translate}}</span>
<span>{{'IMMUTABLE_TAG.LOWER_LABELS' | translate}}</span>
<span>{{getI18nKey(rule?.tag_selectors[1]?.decoration)|translate}}</span>
<span>{{rule?.tag_selectors[1]?.pattern}}</span>
</ng-container>
</span>
</div>
</div>
</li>
</ul>
<div class="v-center clr-row" [ngClass]="{'pt-1':rules?.length > 0}">
<div class="clr-col-2 flex-150"></div>
<div class="add-rule-btn">
<button [disabled]="rules?.length >= 15" id="add-rule" class="btn btn-primary " (click)="openAddRule()">{{'IMMUTABLE_TAG.ADD_RULE' | translate}}</button>
</div>
</div>
</div>
</div>
<app-add-acceleration-rule #addRule [rules]="rules" [projectId]="projectId" (clickAdd)="clickAdd($event)"></app-add-acceleration-rule>
<div class="backdrop-transparent" (click)="ruleIndex = -1" *ngIf="ruleIndex !== -1"></div>

View File

@ -0,0 +1,45 @@
.ml-5 {
margin-left: 5px;
}
.dropdown-toggle {
outline: none;
padding-left: 0;
}
.backdrop-transparent {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
opacity: 0;
z-index: 999;
}
.v-center {
height: 48px;
line-height: 48px;
}
.font-size-54 {
font-size: 0.541667rem;
}
.flex-150 {
flex: 0 0 150px;
}
.rule-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.541667rem;
}
.ml-5 {
margin-left: 5px;
}
.color-97 {
color: #979797;
}
.add-rule-btn {
min-width: 120px;
padding-left: 12px;
}

View File

@ -0,0 +1,149 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ImmutableRetentionRule } from "../tag-retention/retention";
import { ErrorHandler } from "../../../../shared/units/error-handler";
import { ImmutableTagService } from '../immutable-tag/immutable-tag.service';
import { AddAccelerationRuleComponent } from './add-rule/add-acceleration-rule.component';
@Component({
selector: 'app-tag-acceleration',
templateUrl: './tag-acceleration.component.html',
styleUrls: ['./tag-acceleration.component.scss']
})
export class TagAccelerationComponent implements OnInit {
projectId: number;
selectedItem: any = null;
ruleIndex: number = -1;
index: number = -1;
get rules(): ImmutableRetentionRule[] {
return (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || []);
}
editIndex: number;
loadingRule: boolean = false;
@ViewChild('addRule') addRuleComponent: AddAccelerationRuleComponent;
constructor(
private route: ActivatedRoute,
public errorHandler: ErrorHandler,
private immutableTagService: ImmutableTagService,
) {
}
ngOnInit() {
this.projectId = +this.route.snapshot.parent.parent.parent.params['id'];
this.getRules();
this.getMetadata();
}
getMetadata() {
}
getRules() {
}
editRuleByIndex(index) {
this.editIndex = index;
this.addRuleComponent.repoSelect =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index].scope_selectors.repository[0].decoration;
this.addRuleComponent.repositories =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.scope_selectors.repository[0].pattern;
this.addRuleComponent.tagsSelect = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`))
|| [])[index].tag_selectors[0].decoration;
this.addRuleComponent.tagsInput = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.tag_selectors[0].pattern;
this.addRuleComponent.repoSelectEditOrigin =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.scope_selectors.repository[0].decoration;
this.addRuleComponent.repositoriesEditOrigin =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.scope_selectors.repository[0].pattern;
this.addRuleComponent.tagsSelectEditOrigin =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.tag_selectors[0].decoration;
this.addRuleComponent.tagsInputEditOrigin =
(JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || [])[index]
.tag_selectors[0].pattern;
this.addRuleComponent.open();
this.addRuleComponent.isAdd = false;
this.ruleIndex = -1;
}
toggleDisable(index) {
this.ruleIndex = -1;
this.loadingRule = true;
setTimeout(() => {
this.loadingRule = false;
const rules = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || []);
rules[index].disabled = !rules[index].disabled;
localStorage.setItem(`MockedRules${this.projectId}`, JSON.stringify(rules));
}, 500);
}
deleteRule(index) {
// // if rules is empty, clear schedule.
this.ruleIndex = -1;
this.loadingRule = true;
this.loadingRule = true;
this.addRuleComponent.close();
setTimeout(() => {
const rules = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || []);
rules.splice(index, 1);
localStorage.setItem(`MockedRules${this.projectId}`, JSON.stringify(rules));
this.loadingRule = false;
}, 500);
}
openAddRule() {
this.addRuleComponent.open();
this.addRuleComponent.repoSelect = 'repoMatches';
this.addRuleComponent.repositories = '**';
this.addRuleComponent.tagsSelect = 'matches';
this.addRuleComponent.tagsInput = "**";
this.addRuleComponent.isAdd = true;
}
openEditor(index) {
if (this.ruleIndex !== index) {
this.ruleIndex = index;
} else {
this.ruleIndex = -1;
}
}
refreshAfterCreatRetention() {
}
clickAdd(e) {
if (e.isAdd) {
this.loadingRule = true;
this.addRuleComponent.close();
setTimeout(() => {
const rules = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || []);
rules.push(e.rule);
localStorage.setItem(`MockedRules${this.projectId}`, JSON.stringify(rules));
this.loadingRule = false;
}, 500);
} else {
this.loadingRule = true;
this.addRuleComponent.close();
setTimeout(() => {
const rules = (JSON.parse(localStorage.getItem(`MockedRules${this.projectId}`)) || []);
rules[this.editIndex] = e.rule;
localStorage.setItem(`MockedRules${this.projectId}`, JSON.stringify(rules));
this.loadingRule = false;
}, 500);
}
}
formatPattern(pattern: string): string {
let str: string = pattern;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
return str;
}
getI18nKey(str: string) {
return this.immutableTagService.getI18nKey(str);
}
}

View File

@ -7,5 +7,9 @@
<input type="radio" name="btn-group-demo-radios" id="btn-immutable">
<label class="strategy-nav-link" for="btn-immutable">{{'PROJECT_DETAIL.IMMUTABLE_TAG' | translate}}</label>
</div>
<div class="radio btn" routerLink="tag-acceleration" routerLinkActive="checked">
<input type="radio" name="btn-group-demo-radios" id="btn-acceleration">
<label class="strategy-nav-link" for="btn-acceleration">TAG ACCELERATION</label>
</div>
</div>
<router-outlet></router-outlet>

View File

@ -9,6 +9,8 @@ import { ImmutableTagComponent } from "./immutable-tag/immutable-tag.component";
import { ImmutableTagService } from "./immutable-tag/immutable-tag.service";
import { AddImmutableRuleComponent } from "./immutable-tag/add-rule/add-immutable-rule.component";
import { TagRetentionTasksComponent } from './tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component';
import { TagAccelerationComponent } from './tag-acceleration/tag-acceleration.component';
import { AddAccelerationRuleComponent } from './tag-acceleration/add-rule/add-acceleration-rule.component';
const routes: Routes = [
@ -24,6 +26,10 @@ const routes: Routes = [
path: 'immutable-tag',
component: ImmutableTagComponent
},
{
path: 'tag-acceleration',
component: TagAccelerationComponent
},
{ path: '', redirectTo: 'tag-retention', pathMatch: 'full' },
]
}
@ -39,7 +45,9 @@ const routes: Routes = [
AddRuleComponent,
ImmutableTagComponent,
AddImmutableRuleComponent,
TagRetentionTasksComponent
TagRetentionTasksComponent,
TagAccelerationComponent,
AddAccelerationRuleComponent
],
providers: [
TagRetentionService,