mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 22:57:38 +01:00
Modify ui to fix some bugs
Signed-off-by: sshijun <sshijun@vmware.com>
This commit is contained in:
parent
71080d468c
commit
e269786c45
@ -1,5 +1,5 @@
|
||||
|
||||
import {debounceTime} from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -71,7 +71,8 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.searchSub = this.searchTerms.pipe(
|
||||
debounceTime(deBounceTime))
|
||||
debounceTime(deBounceTime),
|
||||
distinctUntilChanged())
|
||||
.subscribe(term => {
|
||||
this.searchTrigger.triggerSearch(term);
|
||||
});
|
||||
|
@ -18,11 +18,15 @@ describe('SearchResultComponent', () => {
|
||||
let fakeMessageHandlerService = null;
|
||||
let fakeSearchTriggerService = {
|
||||
searchTriggerChan$: {
|
||||
subscribe: function () {
|
||||
pipe() {
|
||||
return {
|
||||
subscribe() {
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
searchCloseChan$: {
|
||||
subscribe: function () {
|
||||
subscribe() {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from "rxjs";
|
||||
import { Observable, Subscription } from "rxjs";
|
||||
|
||||
import { GlobalSearchService } from './global-search.service';
|
||||
import { SearchResults } from './search-results';
|
||||
@ -20,6 +20,7 @@ import { SearchTriggerService } from './search-trigger.service';
|
||||
|
||||
import { AppConfigService } from './../../app-config.service';
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { filter, switchMap } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: "search-result",
|
||||
@ -54,9 +55,35 @@ export class SearchResultComponent implements OnInit, OnDestroy {
|
||||
private appConfigService: AppConfigService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(term => {
|
||||
this.doSearch(term);
|
||||
});
|
||||
this.searchSub = this.searchTrigger.searchTriggerChan$
|
||||
.pipe(filter(term => {
|
||||
if (term === "") {
|
||||
this.searchResults.project = [];
|
||||
this.searchResults.repository = [];
|
||||
if (this.withHelmChart) {
|
||||
this.searchResults.chart = [];
|
||||
}
|
||||
}
|
||||
return !!(term && term.trim());
|
||||
}),
|
||||
switchMap(term => {
|
||||
// Confirm page is displayed
|
||||
if (!this.stateIndicator) {
|
||||
this.show();
|
||||
}
|
||||
this.currentTerm = term;
|
||||
// Show spinner
|
||||
this.onGoing = true;
|
||||
return this.search.doSearch(term);
|
||||
}))
|
||||
.subscribe(searchResults => {
|
||||
this.onGoing = false;
|
||||
this.originalCopy = searchResults; // Keep the original data
|
||||
this.searchResults = this.clone(searchResults);
|
||||
}, error => {
|
||||
this.onGoing = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
this.closeSearchSub = this.searchTrigger.searchCloseChan$.subscribe(close => {
|
||||
this.close();
|
||||
});
|
||||
@ -111,7 +138,7 @@ export class SearchResultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Call search service to complete the search request
|
||||
doSearch(term: string): void {
|
||||
doSearch(term: string): void {
|
||||
// Only search none empty term
|
||||
if (!term || term.trim() === "") {
|
||||
return;
|
||||
|
@ -8,8 +8,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isNameValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="create_project_name" class="clr-input input-width"
|
||||
required pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" #projectName="ngModel" autocomplete="off"
|
||||
(keyup)='handleValidation()'>
|
||||
required pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" #projectName autocomplete="off">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||
</div>
|
||||
|
@ -11,20 +11,20 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges, AfterViewInit, ElementRef
|
||||
} from "@angular/core";
|
||||
import { NgForm, Validators, AbstractControl } from "@angular/forms";
|
||||
import { Subject } from "rxjs";
|
||||
import { fromEvent, Subject, Subscription } from "rxjs";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
@ -39,7 +39,7 @@ import { clone, getByte, GetIntegerAndUnit, validateCountLimit, validateLimit }
|
||||
templateUrl: "create-project.component.html",
|
||||
styleUrls: ["create-project.scss"]
|
||||
})
|
||||
export class CreateProjectComponent implements OnInit, OnChanges, OnDestroy {
|
||||
export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
|
||||
projectForm: NgForm;
|
||||
|
||||
@ -62,49 +62,66 @@ export class CreateProjectComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
staticBackdrop = true;
|
||||
closable = false;
|
||||
|
||||
isNameValid = true;
|
||||
isNameExisted: boolean = false;
|
||||
nameTooltipText = "PROJECT.NAME_TOOLTIP";
|
||||
checkOnGoing = false;
|
||||
proNameChecker: Subject<string> = new Subject<string>();
|
||||
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@Input() quotaObj: QuotaHardInterface;
|
||||
@Input() isSystemAdmin: boolean;
|
||||
@ViewChild(InlineAlertComponent, {static: true})
|
||||
inlineAlert: InlineAlertComponent;
|
||||
|
||||
@ViewChild('projectName', {static: false}) projectNameInput: ElementRef;
|
||||
checkNameSubscribe: Subscription;
|
||||
constructor(private projectService: ProjectService,
|
||||
private translateService: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.proNameChecker.pipe(
|
||||
debounceTime(300))
|
||||
.subscribe((name: string) => {
|
||||
let cont = this.currentForm.controls["create_project_name"];
|
||||
if (cont) {
|
||||
this.isNameValid = cont.valid;
|
||||
if (this.isNameValid) {
|
||||
// Check exiting from backend
|
||||
this.checkOnGoing = true;
|
||||
this.projectService
|
||||
.checkProjectExists(cont.value)
|
||||
.subscribe(() => {
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.checkNameSubscribe) {
|
||||
this.checkNameSubscribe = fromEvent(this.projectNameInput.nativeElement, 'input').pipe(
|
||||
map((e: any) => e.target.value),
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
return this.currentForm.controls["create_project_name"].valid && name.length > 0;
|
||||
}),
|
||||
switchMap(name => {
|
||||
// Check exiting from backend
|
||||
this.checkOnGoing = true;
|
||||
this.isNameExisted = false;
|
||||
return this.projectService.checkProjectExists(name);
|
||||
})).subscribe(response => {
|
||||
// Project existing
|
||||
this.isNameValid = false;
|
||||
this.nameTooltipText = "PROJECT.NAME_ALREADY_EXISTS";
|
||||
if (!(response && response.status === 404)) {
|
||||
this.isNameExisted = true;
|
||||
}
|
||||
this.checkOnGoing = false;
|
||||
}, error => {
|
||||
}, error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
} else {
|
||||
this.nameTooltipText = "PROJECT.NAME_TOOLTIP";
|
||||
}
|
||||
this.isNameExisted = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
get isNameValid(): boolean {
|
||||
if (!this.currentForm || !this.currentForm.controls || !this.currentForm.controls["create_project_name"]) {
|
||||
return true;
|
||||
}
|
||||
if (!(this.currentForm.controls["create_project_name"].dirty || this.currentForm.controls["create_project_name"].touched)) {
|
||||
return true;
|
||||
}
|
||||
if (this.checkOnGoing) {
|
||||
return true;
|
||||
}
|
||||
if (this.currentForm.controls["create_project_name"].errors) {
|
||||
this.nameTooltipText = 'PROJECT.NAME_TOOLTIP';
|
||||
return false;
|
||||
}
|
||||
if (this.isNameExisted) {
|
||||
this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes && changes["quotaObj"] && changes["quotaObj"].currentValue) {
|
||||
this.countLimit = this.quotaObj.count_per_project;
|
||||
@ -141,7 +158,10 @@ export class CreateProjectComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.proNameChecker.unsubscribe();
|
||||
if (this.checkNameSubscribe) {
|
||||
this.checkNameSubscribe.unsubscribe();
|
||||
this.checkNameSubscribe = null;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
@ -173,11 +193,11 @@ export class CreateProjectComponent implements OnInit, OnChanges, OnDestroy {
|
||||
newProject() {
|
||||
this.project = new Project();
|
||||
this.hasChanged = false;
|
||||
this.isNameValid = true;
|
||||
|
||||
this.createProjectOpened = true;
|
||||
if (this.currentForm && this.currentForm.controls && this.currentForm.controls["create_project_name"]) {
|
||||
this.currentForm.controls["create_project_name"].reset();
|
||||
}
|
||||
this.inlineAlert.close();
|
||||
|
||||
this.countLimit = this.countDefaultLimit ;
|
||||
this.storageLimit = this.storageDefaultLimit;
|
||||
this.storageLimitUnit = this.storageDefaultLimitUnit;
|
||||
@ -190,14 +210,5 @@ export class CreateProjectComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.isNameValid &&
|
||||
!this.checkOnGoing;
|
||||
}
|
||||
|
||||
// Handle the form validation
|
||||
handleValidation(): void {
|
||||
let cont = this.currentForm.controls["create_project_name"];
|
||||
if (cont) {
|
||||
this.proNameChecker.next(cont.value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -733,10 +733,10 @@
|
||||
},
|
||||
"SUMMARY": {
|
||||
"QUOTAS": "quotas",
|
||||
"PROJECT_REPOSITORY": "Project repositories",
|
||||
"PROJECT_HELM_CHART": "Project Helm Chart",
|
||||
"PROJECT_MEMBER": "Project members",
|
||||
"PROJECT_QUOTAS": "Project quotas",
|
||||
"PROJECT_REPOSITORY": "Repositories",
|
||||
"PROJECT_HELM_CHART": "Helm Chart",
|
||||
"PROJECT_MEMBER": "Members",
|
||||
"PROJECT_QUOTAS": "Quotas",
|
||||
"ARTIFACT_COUNT": "Artifact count",
|
||||
"STORAGE_CONSUMPTION": "Storage consumption",
|
||||
"ADMIN": "Admin(s)",
|
||||
|
@ -734,10 +734,10 @@
|
||||
},
|
||||
"SUMMARY": {
|
||||
"QUOTAS": "quotas",
|
||||
"PROJECT_REPOSITORY": "Project repositories",
|
||||
"PROJECT_HELM_CHART": "Project Helm Chart",
|
||||
"PROJECT_MEMBER": "Project members",
|
||||
"PROJECT_QUOTAS": "Project quotas",
|
||||
"PROJECT_REPOSITORY": "Repositories",
|
||||
"PROJECT_HELM_CHART": "Helm Chart",
|
||||
"PROJECT_MEMBER": "Members",
|
||||
"PROJECT_QUOTAS": "Quotas",
|
||||
"ARTIFACT_COUNT": "Artifact count",
|
||||
"STORAGE_CONSUMPTION": "Storage consumption",
|
||||
"ADMIN": "Admin(s)",
|
||||
|
@ -720,10 +720,10 @@
|
||||
},
|
||||
"SUMMARY": {
|
||||
"QUOTAS": "quotas",
|
||||
"PROJECT_REPOSITORY": "Project repositories",
|
||||
"PROJECT_HELM_CHART": "Project Helm Chart",
|
||||
"PROJECT_MEMBER": "Project members",
|
||||
"PROJECT_QUOTAS": "Project quotas",
|
||||
"PROJECT_REPOSITORY": "Repositories",
|
||||
"PROJECT_HELM_CHART": "Helm Chart",
|
||||
"PROJECT_MEMBER": "Members",
|
||||
"PROJECT_QUOTAS": "Quotas",
|
||||
"ARTIFACT_COUNT": "Artifact count",
|
||||
"STORAGE_CONSUMPTION": "Storage consumption",
|
||||
"ADMIN": "Admin(s)",
|
||||
|
@ -729,10 +729,10 @@
|
||||
},
|
||||
"SUMMARY": {
|
||||
"QUOTAS": "quotas",
|
||||
"PROJECT_REPOSITORY": "Project repositories",
|
||||
"PROJECT_HELM_CHART": "Project Helm Chart",
|
||||
"PROJECT_MEMBER": "Project members",
|
||||
"PROJECT_QUOTAS": "Project quotas",
|
||||
"PROJECT_REPOSITORY": "Repositories",
|
||||
"PROJECT_HELM_CHART": "Helm Chart",
|
||||
"PROJECT_MEMBER": "Members",
|
||||
"PROJECT_QUOTAS": "Quotas",
|
||||
"ARTIFACT_COUNT": "Artifact count",
|
||||
"STORAGE_CONSUMPTION": "Storage consumption",
|
||||
"ADMIN": "Admin(s)",
|
||||
|
@ -734,10 +734,10 @@
|
||||
},
|
||||
"SUMMARY": {
|
||||
"QUOTAS": "容量",
|
||||
"PROJECT_REPOSITORY": "项目镜像仓库",
|
||||
"PROJECT_HELM_CHART": "项目 Helm Chart",
|
||||
"PROJECT_MEMBER": "项目成员",
|
||||
"PROJECT_QUOTAS": "项目容量",
|
||||
"PROJECT_REPOSITORY": "镜像仓库",
|
||||
"PROJECT_HELM_CHART": "Helm Chart",
|
||||
"PROJECT_MEMBER": "成员",
|
||||
"PROJECT_QUOTAS": "容量",
|
||||
"ARTIFACT_COUNT": "Artifact 数量",
|
||||
"STORAGE_CONSUMPTION": "存储消耗",
|
||||
"ADMIN": "管理员",
|
||||
|
@ -20,8 +20,8 @@
|
||||
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.CONTENT_TRUST_POLCIY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container id="prevent-vulenrability-image">
|
||||
<label><span>{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
|
||||
<clr-checkbox-container id="prevent-vulenrability-image" class="margin-top-05">
|
||||
<label></label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.PreventVulImg"
|
||||
name="prevent-vulenrability-image-input" [disabled]="!hasChangeConfigRole" />
|
||||
|
@ -95,4 +95,7 @@
|
||||
flex-direction: column-reverse;
|
||||
font-size: 13px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
.margin-top-05 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
import {throwError as observableThrowError, Observable } from "rxjs";
|
||||
import { throwError as observableThrowError, Observable, of } from "rxjs";
|
||||
import {Injectable, Inject} from "@angular/core";
|
||||
import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http";
|
||||
import { catchError } from "rxjs/operators";
|
||||
@ -169,8 +169,13 @@ export class ProjectDefaultService extends ProjectService {
|
||||
|
||||
public checkProjectExists(projectName: string): Observable<any> {
|
||||
return this.http
|
||||
.head(`/api/projects/?project_name=${projectName}`).pipe(
|
||||
catchError(error => observableThrowError(error)), );
|
||||
.head(`/api/projects/?project_name=${projectName}`).pipe(
|
||||
catchError(error => {
|
||||
if (error && error.status === 404) {
|
||||
return of(error);
|
||||
}
|
||||
return observableThrowError(error);
|
||||
}));
|
||||
}
|
||||
|
||||
public checkProjectMember(projectId: number): Observable<any> {
|
||||
|
Loading…
Reference in New Issue
Block a user