Improve search function for replication and tags

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
AllForNothing 2019-12-16 10:36:37 +08:00
parent 26905baca2
commit 5d12423f74
14 changed files with 109 additions and 105 deletions

View File

@ -67,7 +67,7 @@
</div>
</div>
<div class="cron-selection">
<cron-selection [disabled]="!(retention?.rules?.length > 0)" #cronScheduleComponent [labelCurrent]="label" [labelEdit]='label' [originCron]='originCron()' (inputvalue)="openConfirm($event)"></cron-selection>
<cron-selection [buttonMarginLeft]="'150px'" [disabled]="!(retention?.rules?.length > 0)" #cronScheduleComponent [labelCurrent]="label" [labelEdit]='label' [originCron]='originCron()' (inputvalue)="openConfirm($event)"></cron-selection>
</div>
<div class="clr-row pt-1">
<div class="clr-col-2 pt-2 flex-150"><label class="label-left font-size-54">{{'TAG_RETENTION.RETENTION_RUNS' | translate}}</label></div>

View File

@ -58,18 +58,6 @@
.font-size-54 {
font-size: .541667rem;
}
:host::ng-deep {
.normal-wrapper-box {
.normal-wrapper {
.font-style {
width: 150px!important;
}
}
.btn {
margin-left: 150px!important;
}
}
}
.flex-150 {
flex: 0 0 150px;
max-width: 150px;
@ -89,4 +77,4 @@
}
}
}
}
}

View File

@ -1,6 +1,6 @@
<div class="normal-wrapper-box flex-layout" *ngIf="!isEditMode">
<div class="normal-wrapper">
<span class="font-style">{{ labelCurrent | translate }}</span>
<span [style.width]="buttonMarginLeft" class="font-style">{{ labelCurrent | translate }}</span>
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
@ -17,12 +17,12 @@
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ "SCHEDULE.CRON" | translate }} :</span>
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ oriCron }}</span>
</div>
<button [disabled]="disabled" class="btn btn-primary btn-sm" (click)="editSchedule()" id="editSchedule">
<button [style.margin-left]="buttonMarginLeft" [disabled]="disabled" class="btn btn-primary btn-sm" (click)="editSchedule()" id="editSchedule">
{{ "BUTTON.EDIT" | translate }}
</button>
</div>
<div class="setting-wrapper flex-layout" *ngIf="isEditMode">
<span class="font-style">{{ labelEdit | translate }}</span>
<span [style.width]="buttonMarginLeft" class="font-style">{{ labelEdit | translate }}</span>
<div class="select select-schedule clr-select-wrapper">
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
@ -49,7 +49,7 @@
</a>
</div>
<div class="confirm-button">
<button class="btn btn-primary btn-sm"
<button [style.margin-left]="buttonMarginLeft" class="btn btn-primary btn-sm"
(click)="save()" id="config-save">
{{ "BUTTON.SAVE" | translate }}
</button>
@ -57,4 +57,4 @@
{{ "BUTTON.CANCEL" | translate }}
</button>
</div>
</div>
</div>

View File

@ -8,10 +8,6 @@
.normal-wrapper {
position: absolute;
width: 700px;
> span:first-child {
width: 200px;
}
> span:not(:first-child) {
margin-right: 18px;
}
@ -20,18 +16,14 @@
}
}
button {
margin: 35px 20px 0 200px;
margin: 35px 20px 0;
}
}
.setting-wrapper {
position: relative;
> span:first-child {
width: 200px;
}
.confirm-button {
margin: 20px 0px 0 200px;
margin: 20px 0 0;
}
*:not(:first-child) {
@ -65,7 +57,6 @@
display: inline-block;
color: #000;
font-size: .541667rem;
width: 200px;
}
span.required {

View File

@ -28,6 +28,7 @@ export class CronScheduleComponent implements OnChanges {
@Input() labelEdit: string;
@Input() labelCurrent: string;
@Input() disabled: boolean;
@Input() buttonMarginLeft: string = '200px';
dateInvalid: boolean;
originScheduleType: string;
oriCron: string;

View File

@ -1,6 +1,6 @@
<span>
<clr-icon shape="search" size="20" class="search-btn" [class.filter-icon]="isShowSearchBox" (click)="onClick()"></clr-icon>
<input [hidden]="!isShowSearchBox" type="text" class="filter-input clr-input" autofocus (keyup)="valueChange()" (focus)="inputFocus()"
<input [attr.readOnly]="readonly" [hidden]="!isShowSearchBox" type="text" class="filter-input clr-input" autofocus (keyup)="valueChange()" (focus)="inputFocus()"
placeholder="{{placeHolder}}" [(ngModel)]="currentValue" />
<span class="filter-divider" *ngIf="withDivider"></span>
</span>
</span>

View File

@ -27,7 +27,7 @@ export class FilterComponent implements OnInit {
@Output() private filterEvt = new EventEmitter<string>();
@Output() private openFlag = new EventEmitter<boolean>();
@Input() readonly: string = null;
@Input() currentValue: string;
@Input("filterPlaceholder")
public set flPlaceholder(placeHolder: string) {

View File

@ -1,45 +1,51 @@
<form [formGroup]="imageNameForm" class="clr-form clr-form-compact" (mouseleave)="leaveProjectInput()">
<div class="clr-form-control clr-row">
<label for="project-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'PROJECT.NAME' | translate }}</label>
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
<form [formGroup]="imageNameForm" class="clr-form clr-form-horizontal" (mouseleave)="leaveProjectInput()">
<div class="clr-form-control">
<label class="required clr-control-label">{{ 'PROJECT.NAME' | translate }}</label>
<div class="clr-control-container"
[class.clr-error]="noProjectInfo && (projectName.dirty || projectName.touched)">
<div class="clr-input-wrapper">
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]='noProjectInfo'>
<input type="text"
id="project-name"
class="clr-input"
(keyup)='validateProjectName()'
(blur)='blurProjectInput()'
formControlName="projectName"/>
<span *ngIf="noProjectInfo && (projectName.dirty || projectName.touched)" class="tooltip-content">{{noProjectInfo | translate}}</span>
</label>
<input type="text"
id="project-name"
class="clr-input w-90"
(keyup)='validateProjectName()'
(blur)='blurProjectInput()'
formControlName="projectName" autocomplete="off" />
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
<div class="select-box" [style.display]="selectedProjectList.length ? 'block' : 'none'">
<ul>
<li *ngFor="let project of selectedProjectList" (click)="selectedProjectName(project?.name)">{{project?.name}}</li>
<li *ngFor="let project of selectedProjectList"
(click)="selectedProjectName(project?.name)">{{project?.name}}</li>
</ul>
</div>
</div>
<clr-control-error *ngIf="noProjectInfo && (projectName.dirty || projectName.touched)"
class="tooltip-content">
{{noProjectInfo | translate}}
</clr-control-error>
</div>
</div>
<div class="clr-form-control clr-row">
<label for="repo-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'REPOSITORY.REPO_NAME' | translate }}</label>
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
<div class="clr-form-control">
<label class="required clr-control-label">{{ 'REPOSITORY.REPO_NAME' | translate }}</label>
<div class="clr-control-container" [class.clr-error]="repoName.invalid && (repoName.dirty || repoName.touched)">
<div class="clr-input-wrapper">
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='repoName.invalid && (repoName.dirty || repoName.touched)'>
<input type="text" id="repo-name" class="clr-input" formControlName="repoName" />
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'RETAG.TIP_REPO' | translate }}</span>
</label>
<input type="text" id="repo-name" class="clr-input w-90" formControlName="repoName" autocomplete="off" />
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">
{{ 'RETAG.TIP_REPO' | translate }}
</clr-control-error>
</div>
</div>
<div class="clr-form-control clr-row">
<label for="tag-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'REPOSITORY.TAG' | translate }}</label>
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
<div class="clr-form-control">
<label class="required clr-control-label">{{ 'REPOSITORY.TAG' | translate }}</label>
<div class="clr-control-container" [class.clr-error]="tagName.invalid && (tagName.dirty || tagName.touched)">
<div class="clr-input-wrapper">
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='tagName.invalid && (tagName.dirty || tagName.touched)'>
<input type="text" id="tag-name" class="clr-input" formControlName="tagName" />
<span *ngIf="tagName.invalid && (tagName.dirty || tagName.touched)" class="tooltip-content">{{ 'RETAG.TIP_TAG' | translate }}</span>
</label>
<input type="text" id="tag-name" class="clr-input w-90" formControlName="tagName" autocomplete="off" />
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="tagName.invalid && (tagName.dirty || tagName.touched)" class="tooltip-content">
{{ 'RETAG.TIP_TAG' | translate }}
</clr-control-error>
</div>
</div>
</form>
</form>

View File

@ -42,13 +42,9 @@
width: 100%;
}
}
label.required {
&:after {
content: '*';
font-size: .58479532rem;
line-height: .5rem;
color: #c92100;
margin-left: .25rem;
}
}
.clr-control-container {
width: 60%;
}
.w-90 {
width: 90%;
}

View File

@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Project } from "../project-policy-config/project";
import { Subject } from "rxjs/index";
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs/operators";
import { ProjectService } from "../../services/project.service";
import { AbstractControl, FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ErrorHandler } from "../../utils/error-handler/error-handler";
@ -42,36 +42,33 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
])],
});
}
ngOnInit(): void {
this.proNameChecker
.pipe(debounceTime(200))
.pipe(distinctUntilChanged())
.subscribe((name: string) => {
this.noProjectInfo = "";
this.selectedProjectList = [];
const prolist: any = this.proService.listProjects(name, undefined);
if (prolist.subscribe) {
prolist.subscribe(response => {
if (response.body) {
this.selectedProjectList = response.body.slice(0, 10);
// if input project name exist in the project list
let exist = response.body.find((data: any) => data.name === name);
if (!exist) {
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
} else {
this.noProjectInfo = "";
}
} else {
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
}
}, (error: any) => {
this.errorHandler.error(error);
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
});
.pipe(distinctUntilChanged(),
switchMap(name => {
this.noProjectInfo = "";
this.selectedProjectList = [];
return this.proService.listProjects(name, undefined);
})
).subscribe(response => {
if (response.body) {
this.selectedProjectList = response.body.slice(0, 10);
// if input project name exist in the project list
let exist = response.body.find((data: any) => data.name === this.imageNameForm.controls["projectName"].value);
if (!exist) {
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
} else {
this.errorHandler.error("not Observable type");
this.noProjectInfo = "";
}
});
} else {
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
}
}, (error: any) => {
this.errorHandler.error(error);
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
});
}
validateProjectName(): void {

View File

@ -2,7 +2,7 @@
<div>
<div class="row flex-items-xs-between rightPos">
<div class="flex-xs-middle option-right">
<hbr-filter id="filter-rules" [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filterEvt)="doSearchRules($event)"
<hbr-filter id="filter-rules" [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}'
[currentValue]="search.ruleName"></hbr-filter>
<span class="refresh-btn" (click)="refreshRules()">
<clr-icon shape="refresh"></clr-icon>
@ -93,4 +93,4 @@
(goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
<confirmation-dialog #StopConfirmDialog (confirmAction)="confirmStop($event)"></confirmation-dialog>
</div>
</div>

View File

@ -21,7 +21,7 @@ import {
EventEmitter
} from "@angular/core";
import { Comparator, State } from "../../services/interface";
import { finalize, catchError, map } from "rxjs/operators";
import { finalize, catchError, map, debounceTime, distinctUntilChanged, switchMap, delay } from "rxjs/operators";
import { Subscription, forkJoin, timer, Observable, throwError as observableThrowError, observable } from "rxjs";
import { TranslateService } from "@ngx-translate/core";
@ -62,6 +62,7 @@ import {
import { OperationService } from "../operation/operation.service";
import { Router } from "@angular/router";
import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils";
import { FilterComponent } from "../filter/filter.component";
const ONE_HOUR_SECONDS: number = 3600;
const ONE_MINUTE_SECONDS: number = 60;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@ -146,6 +147,9 @@ export class ReplicationComponent implements OnInit, OnDestroy {
currentState: State;
jobsLoading: boolean = false;
timerDelay: Subscription;
@ViewChild(FilterComponent, {static: true})
filterComponent: FilterComponent;
searchSub: Subscription;
constructor(
private router: Router,
@ -160,6 +164,23 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
ngOnInit() {
if (!this.searchSub) {
this.searchSub = this.filterComponent.filterTerms.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap( ruleName => {
this.loading = true;
return this.replicationService.getReplicationRules(this.projectId, ruleName);
})
).subscribe(rules => {
this.hideJobs();
this.listReplicationRule.changedRules = rules || [];
this.loading = false;
}, error => {
this.errorHandler.error(error);
this.loading = false;
});
}
this.currentRuleStatus = this.ruleStatus[0];
}
@ -167,6 +188,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
if (this.timerDelay) {
this.timerDelay.unsubscribe();
}
if (this.searchSub) {
this.searchSub.unsubscribe();
this.searchSub = null;
}
}
// open replication rule

View File

@ -26,11 +26,11 @@
<div>
<div class="row flex-items-xs-right rightPos">
<div id="filterArea">
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='filterLabelPieceWidth'>
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='32'>
<hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece>
</div>
<div class="flex-xs-middle">
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filterEvt)="doSearchTagNames($event)" (openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
<hbr-filter [readonly]="'readonly'" [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filterEvt)="doSearchTagNames($event)" (openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
<div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
<a class="filterClose" (click)="closeFilter()">&times;</a>
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>

View File

@ -126,7 +126,7 @@
.filterLabelPiece {
position: absolute;
top: 4px;
top: 8px;
z-index: 1;
}
@ -275,4 +275,4 @@ clr-datagrid {
clr-datagrid {
height: auto !important;
}
}