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> </div>
<div class="cron-selection"> <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>
<div class="clr-row pt-1"> <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> <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-54 {
font-size: .541667rem; font-size: .541667rem;
} }
:host::ng-deep {
.normal-wrapper-box {
.normal-wrapper {
.font-style {
width: 150px!important;
}
}
.btn {
margin-left: 150px!important;
}
}
}
.flex-150 { .flex-150 {
flex: 0 0 150px; flex: 0 0 150px;
max-width: 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-box flex-layout" *ngIf="!isEditMode">
<div class="normal-wrapper"> <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> <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"> <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> <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">{{ "SCHEDULE.CRON" | translate }} :</span>
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ oriCron }}</span> <span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ oriCron }}</span>
</div> </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.EDIT" | translate }}
</button> </button>
</div> </div>
<div class="setting-wrapper flex-layout" *ngIf="isEditMode"> <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"> <div class="select select-schedule clr-select-wrapper">
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType"> <select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option> <option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
@ -49,7 +49,7 @@
</a> </a>
</div> </div>
<div class="confirm-button"> <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"> (click)="save()" id="config-save">
{{ "BUTTON.SAVE" | translate }} {{ "BUTTON.SAVE" | translate }}
</button> </button>
@ -57,4 +57,4 @@
{{ "BUTTON.CANCEL" | translate }} {{ "BUTTON.CANCEL" | translate }}
</button> </button>
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<span> <span>
<clr-icon shape="search" size="20" class="search-btn" [class.filter-icon]="isShowSearchBox" (click)="onClick()"></clr-icon> <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" /> placeholder="{{placeHolder}}" [(ngModel)]="currentValue" />
<span class="filter-divider" *ngIf="withDivider"></span> <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 filterEvt = new EventEmitter<string>();
@Output() private openFlag = new EventEmitter<boolean>(); @Output() private openFlag = new EventEmitter<boolean>();
@Input() readonly: string = null;
@Input() currentValue: string; @Input() currentValue: string;
@Input("filterPlaceholder") @Input("filterPlaceholder")
public set flPlaceholder(placeHolder: string) { public set flPlaceholder(placeHolder: string) {

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="row flex-items-xs-between rightPos"> <div class="row flex-items-xs-between rightPos">
<div class="flex-xs-middle option-right"> <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> [currentValue]="search.ruleName"></hbr-filter>
<span class="refresh-btn" (click)="refreshRules()"> <span class="refresh-btn" (click)="refreshRules()">
<clr-icon shape="refresh"></clr-icon> <clr-icon shape="refresh"></clr-icon>
@ -93,4 +93,4 @@
(goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule> (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog> <confirmation-dialog #replicationConfirmDialog (confirmAction)="confirmReplication($event)"></confirmation-dialog>
<confirmation-dialog #StopConfirmDialog (confirmAction)="confirmStop($event)"></confirmation-dialog> <confirmation-dialog #StopConfirmDialog (confirmAction)="confirmStop($event)"></confirmation-dialog>
</div> </div>

View File

@ -21,7 +21,7 @@ import {
EventEmitter EventEmitter
} from "@angular/core"; } from "@angular/core";
import { Comparator, State } from "../../services/interface"; 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 { Subscription, forkJoin, timer, Observable, throwError as observableThrowError, observable } from "rxjs";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
@ -62,6 +62,7 @@ import {
import { OperationService } from "../operation/operation.service"; import { OperationService } from "../operation/operation.service";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils"; import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils";
import { FilterComponent } from "../filter/filter.component";
const ONE_HOUR_SECONDS: number = 3600; const ONE_HOUR_SECONDS: number = 3600;
const ONE_MINUTE_SECONDS: number = 60; const ONE_MINUTE_SECONDS: number = 60;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS; const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@ -146,6 +147,9 @@ export class ReplicationComponent implements OnInit, OnDestroy {
currentState: State; currentState: State;
jobsLoading: boolean = false; jobsLoading: boolean = false;
timerDelay: Subscription; timerDelay: Subscription;
@ViewChild(FilterComponent, {static: true})
filterComponent: FilterComponent;
searchSub: Subscription;
constructor( constructor(
private router: Router, private router: Router,
@ -160,6 +164,23 @@ export class ReplicationComponent implements OnInit, OnDestroy {
} }
ngOnInit() { 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]; this.currentRuleStatus = this.ruleStatus[0];
} }
@ -167,6 +188,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
if (this.timerDelay) { if (this.timerDelay) {
this.timerDelay.unsubscribe(); this.timerDelay.unsubscribe();
} }
if (this.searchSub) {
this.searchSub.unsubscribe();
this.searchSub = null;
}
} }
// open replication rule // open replication rule

View File

@ -26,11 +26,11 @@
<div> <div>
<div class="row flex-items-xs-right rightPos"> <div class="row flex-items-xs-right rightPos">
<div id="filterArea"> <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> <hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece>
</div> </div>
<div class="flex-xs-middle"> <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"> <div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
<a class="filterClose" (click)="closeFilter()">&times;</a> <a class="filterClose" (click)="closeFilter()">&times;</a>
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label> <label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>

View File

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