From 4f66279c33f930bebc98df511972847cc13f49cf Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Mon, 15 May 2017 14:35:38 -0700 Subject: [PATCH 1/7] Fail authentication when username is empty (#2300) (#2303) --- src/ui/auth/ldap/ldap.go | 4 ++++ src/ui/auth/ldap/ldap_test.go | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/ui/auth/ldap/ldap.go b/src/ui/auth/ldap/ldap.go index 235013c3d..6b8942176 100644 --- a/src/ui/auth/ldap/ldap.go +++ b/src/ui/auth/ldap/ldap.go @@ -36,6 +36,10 @@ const metaChars = "&|!=~*<>()" func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { p := m.Principal + if len(strings.TrimSpace(p)) == 0 { + log.Debugf("LDAP authentication failed for empty user id.") + return nil, nil + } for _, c := range metaChars { if strings.ContainsRune(p, c) { return nil, fmt.Errorf("the principal contains meta char: %q", c) diff --git a/src/ui/auth/ldap/ldap_test.go b/src/ui/auth/ldap/ldap_test.go index e979c9d11..6563f4f68 100644 --- a/src/ui/auth/ldap/ldap_test.go +++ b/src/ui/auth/ldap/ldap_test.go @@ -131,4 +131,13 @@ func TestAuthenticate(t *testing.T) { if user != nil { t.Errorf("Nil user expected for wrong password") } + person.Principal = "" + person.Password = "" + user, err = auth.Authenticate(person) + if err != nil { + t.Errorf("unexpected ldap error: %v", err) + } + if user != nil { + t.Errorf("Nil user for empty credentials") + } } From 153d807fca4bf52032a7e1f753f10aef20f9ae4a Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Mon, 15 May 2017 17:57:20 -0700 Subject: [PATCH 2/7] Refine the access level of project creation --- .../create-project.component.html | 57 ++++++------ .../create-project.component.ts | 86 ++++++++++--------- .../project/create-project/create-project.css | 9 ++ src/ui_ng/src/i18n/lang/en-us-lang.json | 1 + src/ui_ng/src/i18n/lang/zh-cn-lang.json | 1 + 5 files changed, 85 insertions(+), 69 deletions(-) diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html index 7ebb4cc55..dfd036b2c 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.html +++ b/src/ui_ng/src/app/project/create-project/create-project.component.html @@ -1,12 +1,12 @@ - - - + + \ No newline at end of file diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index 1643e83d4..053a74277 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -26,10 +26,10 @@ import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'create-project', templateUrl: 'create-project.component.html', - styleUrls: [ 'create-project.css' ] + styleUrls: ['create-project.css'] }) export class CreateProjectComponent implements AfterViewChecked { - + projectForm: NgForm; @ViewChild('projectForm') @@ -39,7 +39,7 @@ export class CreateProjectComponent implements AfterViewChecked { initVal: Project = new Project(); createProjectOpened: boolean; - + hasChanged: boolean; staticBackdrop: boolean = true; @@ -49,60 +49,64 @@ export class CreateProjectComponent implements AfterViewChecked { @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent; - constructor(private projectService: ProjectService, - private translateService: TranslateService, - private messageHandlerService: MessageHandlerService) {} + constructor(private projectService: ProjectService, + private translateService: TranslateService, + private messageHandlerService: MessageHandlerService) { } + + public get accessLevelDisplayText(): string { + return this.project.public ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE'; + } onSubmit() { this.projectService - .createProject(this.project.name, this.project.public ? 1 : 0) - .subscribe( - status=>{ - this.create.emit(true); - this.messageHandlerService.showSuccess('PROJECT.CREATED_SUCCESS'); + .createProject(this.project.name, this.project.public ? 1 : 0) + .subscribe( + status => { + this.create.emit(true); + this.messageHandlerService.showSuccess('PROJECT.CREATED_SUCCESS'); + this.createProjectOpened = false; + }, + error => { + let errorMessage: string; + if (error instanceof Response) { + switch (error.status) { + case 409: + this.translateService.get('PROJECT.NAME_ALREADY_EXISTS').subscribe(res => errorMessage = res); + break; + case 400: + this.translateService.get('PROJECT.NAME_IS_ILLEGAL').subscribe(res => errorMessage = res); + break; + default: + this.translateService.get('PROJECT.UNKNOWN_ERROR').subscribe(res => errorMessage = res); + } + if (this.messageHandlerService.isAppLevel(error)) { + this.messageHandlerService.handleError(error); this.createProjectOpened = false; - }, - error=>{ - let errorMessage: string; - if (error instanceof Response) { - switch(error.status) { - case 409: - this.translateService.get('PROJECT.NAME_ALREADY_EXISTS').subscribe(res=>errorMessage = res); - break; - case 400: - this.translateService.get('PROJECT.NAME_IS_ILLEGAL').subscribe(res=>errorMessage = res); - break; - default: - this.translateService.get('PROJECT.UNKNOWN_ERROR').subscribe(res=>errorMessage = res); - } - if(this.messageHandlerService.isAppLevel(error)) { - this.messageHandlerService.handleError(error); - this.createProjectOpened = false; - } else { - this.inlineAlert.showInlineError(errorMessage); - } - } - }); + } else { + this.inlineAlert.showInlineError(errorMessage); + } + } + }); } onCancel() { - if(this.hasChanged) { - this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'}); + if (this.hasChanged) { + this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); } else { this.createProjectOpened = false; this.projectForm.reset(); } - + } ngAfterViewChecked(): void { this.projectForm = this.currentForm; - if(this.projectForm) { - this.projectForm.valueChanges.subscribe(data=>{ - for(let i in data) { - let origin = this.initVal[i]; + if (this.projectForm) { + this.projectForm.valueChanges.subscribe(data => { + for (let i in data) { + let origin = this.initVal[i]; let current = data[i]; - if(current && current !== origin) { + if (current && current !== origin) { this.hasChanged = true; break; } else { diff --git a/src/ui_ng/src/app/project/create-project/create-project.css b/src/ui_ng/src/app/project/create-project/create-project.css index 0bcac82e9..9a9500f19 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.css +++ b/src/ui_ng/src/app/project/create-project/create-project.css @@ -1,4 +1,13 @@ .form-group-label-override { font-size: 14px; font-weight: 400; +} + +.access-level-label { + font-size: 14px; + font-weight: 400; + margin-left: -4px; + margin-right: 12px; + top: -6px; + position: relative; } \ No newline at end of file diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 07d76b2f5..2b720049c 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -126,6 +126,7 @@ "PUBLIC_OR_PRIVATE": "Access Level", "REPO_COUNT": "Repositories Count", "CREATION_TIME": "Creation Time", + "ACCESS_LEVEL": "Access Level", "PUBLIC": "Public", "PRIVATE": "Private", "MAKE": "Make", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index c25a4fe7c..658d0cca8 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -126,6 +126,7 @@ "PUBLIC_OR_PRIVATE": "访问级别", "REPO_COUNT": "镜像仓库数", "CREATION_TIME": "创建时间", + "ACCESS_LEVEL": "访问级别", "PUBLIC": "公开", "PRIVATE": "私有", "MAKE": "设为", From 5744b839893e4d84545fa571dfc1f8fb015d43e2 Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Tue, 16 May 2017 09:56:59 -0700 Subject: [PATCH 3/7] Modify password tips to add max length of pwd --- src/ui_ng/src/i18n/lang/en-us-lang.json | 4 ++-- src/ui_ng/src/i18n/lang/es-es-lang.json | 4 ++-- src/ui_ng/src/i18n/lang/zh-cn-lang.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 2b720049c..5a43b6c17 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -39,7 +39,7 @@ "FULL_NAME": "Maximum length should be 20 characters.", "COMMENT": "Length of comment should be less than 20 characters.", "CURRENT_PWD": "Current password is required.", - "PASSWORD": "Password should be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number.", + "PASSWORD": "Password should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.", "CONFIRM_PWD": "Passwords do not match.", "SIGN_IN_USERNAME": "Username is required.", "SIGN_IN_PWD": "Password is required.", @@ -76,7 +76,7 @@ "NEW_PWD": "New Password", "CONFIRM_PWD": "Confirm Password", "SAVE_SUCCESS": "User password changed successfully.", - "PASS_TIPS": "At least 8 characters with 1 uppercase, 1 lowercase and 1 number" + "PASS_TIPS": "8-20 characters long with 1 uppercase, 1 lowercase and 1 number" }, "ACCOUNT_SETTINGS": { "PROFILE": "User Profile", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index 087df658b..be0f93f12 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -39,7 +39,7 @@ "FULL_NAME": "La longitud máxima debería ser de 20 caracteres.", "COMMENT": "La longitud del comentario debería ser menor de 20 caracteres.", "CURRENT_PWD": "Es obligatorio introducir la contraseña actual.", - "PASSWORD": "La contraseña debería tener al menos 8 caracteres, con al menos 1 letra mayúscula, 1 letra minúscula y 1 número.", + "PASSWORD": "La contraseña debería tener de 8 a 20 caracteres, con al menos 1 letra mayúscula, 1 letra minúscula y 1 número.", "CONFIRM_PWD": "Las contraseñas no coinciden.", "SIGN_IN_USERNAME": "El nombre de usuario es obligatorio.", "SIGN_IN_PWD": "La contraseña es obligatoria.", @@ -76,7 +76,7 @@ "NEW_PWD": "Nueva contraseña", "CONFIRM_PWD": "Confirmar contraseña", "SAVE_SUCCESS": "Contraseña de usuario guardada satisfactoriamente.", - "PASS_TIPS": "Al menos 8 caracteres con 1 letra mayúscula, 1 minúscula y 1 número" + "PASS_TIPS": "8-20 caracteres con 1 letra mayúscula, 1 minúscula y 1 número" }, "ACCOUNT_SETTINGS": { "PROFILE": "Perfil de usuario", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index 658d0cca8..7fdf36ecd 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -39,7 +39,7 @@ "FULL_NAME": "长度不能超过20。", "COMMENT": "长度不能超过20。", "CURRENT_PWD": "当前密码为必填项。", - "PASSWORD": "密码长度至少为8且需包含至少一个大写字符,一个小写字符和一个数字。", + "PASSWORD": "密码长度在8到20之间且需包含至少一个大写字符,一个小写字符和一个数字。", "CONFIRM_PWD": "密码输入不一致。", "SIGN_IN_USERNAME": "用户名为必填项。", "SIGN_IN_PWD": "密码为必填项。", @@ -76,7 +76,7 @@ "NEW_PWD": "新密码", "CONFIRM_PWD": "确认密码", "SAVE_SUCCESS": "成功更改用户密码。", - "PASS_TIPS": "至少8个字符且需包含至少一个大写字符、小写字符或者数字" + "PASS_TIPS": "8到20个字符且需包含至少一个大写字符、小写字符或者数字" }, "ACCOUNT_SETTINGS": { "PROFILE": "用户设置", From cf65c39cef65830a06c5936ac9c0eade32075be5 Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Tue, 16 May 2017 12:17:18 -0700 Subject: [PATCH 4/7] Change the validation way of project name --- .../create-project.component.html | 27 +++++++------ .../create-project.component.ts | 38 +++++++++++++++++++ src/ui_ng/src/i18n/lang/en-us-lang.json | 1 + src/ui_ng/src/i18n/lang/es-es-lang.json | 1 + src/ui_ng/src/i18n/lang/zh-cn-lang.json | 3 +- 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html index dfd036b2c..e3a647962 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.html +++ b/src/ui_ng/src/app/project/create-project/create-project.component.html @@ -5,19 +5,22 @@
- -
@@ -36,6 +39,6 @@
\ No newline at end of file diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index 053a74277..774aa9d55 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -45,6 +45,10 @@ export class CreateProjectComponent implements AfterViewChecked { staticBackdrop: boolean = true; closable: boolean = false; + isNameValid: boolean = true; + nameTooltipText: string = 'PROJECT.NAME_TOOLTIP'; + checkOnGoing: boolean = false; + @Output() create = new EventEmitter(); @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent; @@ -129,5 +133,39 @@ export class CreateProjectComponent implements AfterViewChecked { this.inlineAlert.close(); this.projectForm.reset(); } + + public get isValid(): boolean { + return this.currentForm && this.currentForm.valid && this.isNameValid; + } + + //Handle the form validation + handleValidation(flag: boolean): void { + if (flag) { + //validate + let cont = this.currentForm.controls["create_project_name"]; + if (cont) { + this.isNameValid = cont.valid; + if (this.isNameValid && this.hasChanged) { + //Check exiting from backend + this.checkOnGoing = true; + this.projectService + .checkProjectExists(cont.value).toPromise() + .then(() => { + //Project existing + this.isNameValid = false; + this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS'; + this.checkOnGoing = false; + }) + .catch(error => { + this.checkOnGoing = false; + }); + } + } + } else { + //reset + this.isNameValid = true; + this.nameTooltipText = 'PROJECT.NAME_TOOLTIP'; + } + } } diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 5a43b6c17..1447a8b2c 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -136,6 +136,7 @@ "PUBLIC_PROJECTS": "Public Projects", "PROJECT": "Project", "NEW_PROJECT": "New Project", + "NAME_TOOLTIP": "Project name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "NAME_IS_REQUIRED": "Project name is required.", "NAME_MINIMUM_LENGTH": "Project name is too short, it should be greater than 2 characters.", "NAME_ALREADY_EXISTS": "Project name already exists.", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index be0f93f12..d924a54a2 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -136,6 +136,7 @@ "PUBLIC_PROJECTS": "Proyectos Públicos", "PROJECT": "Proyecto", "NEW_PROJECT": "Nuevo proyecto", + "NAME_TOOLTIP": "Project name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "NAME_IS_REQUIRED": "El nombre del proyecto es obligatorio.", "NAME_MINIMUM_LENGTH": "El nombre del proyecto es demasiado corto, debe ser mayor de 2 caracteres.", "NAME_ALREADY_EXISTS": "Ya existe un proyecto con ese nombre.", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index 7fdf36ecd..4b57a3422 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -136,7 +136,8 @@ "PUBLIC_PROJECTS": "公开项目", "PROJECT": "项目", "NEW_PROJECT": "新建项目", - "NAME_IS_REQUIRED": "项目名称为必填项", + "NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。", + "NAME_IS_REQUIRED": "项目名称为必填项。", "NAME_MINIMUM_LENGTH": "项目名称长度过短,至少多于2个字符。", "NAME_ALREADY_EXISTS": "项目名称已存在。", "NAME_IS_ILLEGAL": "项目名称非法。", From 26d20bd9aa3c3208863c9908810e41064bd99823 Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Tue, 16 May 2017 18:01:54 -0700 Subject: [PATCH 5/7] Modify member name checking --- .../global-search/global-search.component.ts | 4 + .../add-member/add-member.component.html | 81 +++++---- .../member/add-member/add-member.component.ts | 171 ++++++++++++------ 3 files changed, 162 insertions(+), 94 deletions(-) diff --git a/src/ui_ng/src/app/base/global-search/global-search.component.ts b/src/ui_ng/src/app/base/global-search/global-search.component.ts index ad3c810a3..e6dab1359 100644 --- a/src/ui_ng/src/app/base/global-search/global-search.component.ts +++ b/src/ui_ng/src/app/base/global-search/global-search.component.ts @@ -72,6 +72,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { if (this.searchSub) { this.searchSub.unsubscribe(); } + + if (this.closeSub) { + this.closeSub.unsubscribe(); + } } //Handle the term inputting event diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.html b/src/ui_ng/src/app/project/member/add-member/add-member.component.html index 9da8644b1..7bc567e28 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.html +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.html @@ -1,41 +1,44 @@ - - - - + + \ No newline at end of file diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts index 96126cbe1..77a2c51e7 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts @@ -11,10 +11,19 @@ // 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 { Component, Input, EventEmitter, Output, ViewChild, AfterViewChecked } from '@angular/core'; +import { + Component, + Input, + EventEmitter, + Output, + ViewChild, + AfterViewChecked, + OnInit, + OnDestroy +} from '@angular/core'; import { Response } from '@angular/http'; import { NgForm } from '@angular/forms'; - + import { MemberService } from '../member.service'; import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service'; @@ -24,18 +33,21 @@ import { TranslateService } from '@ngx-translate/core'; import { Member } from '../member'; +import { Subject } from 'rxjs/Subject'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; + @Component({ selector: 'add-member', templateUrl: 'add-member.component.html', - styleUrls: [ 'add-member.component.css' ] + styleUrls: ['add-member.component.css'] }) -export class AddMemberComponent implements AfterViewChecked { +export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { member: Member = new Member(); - initVal: Member = new Member(); addMemberOpened: boolean; - + memberForm: NgForm; staticBackdrop: boolean = true; @@ -52,49 +64,87 @@ export class AddMemberComponent implements AfterViewChecked { @Input() projectId: number; @Output() added = new EventEmitter(); - constructor(private memberService: MemberService, - private messageHandlerService: MessageHandlerService, - private translateService: TranslateService) {} + isMemberNameValid: boolean = true; + memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED'; + nameChecker: Subject = new Subject(); + checkOnGoing: boolean = false; + + constructor(private memberService: MemberService, + private messageHandlerService: MessageHandlerService, + private translateService: TranslateService) { } + + ngOnInit(): void { + this.nameChecker + .debounceTime(500) + .distinctUntilChanged() + .subscribe((name: string) => { + let cont = this.currentForm.controls['member_name']; + if (cont) { + this.isMemberNameValid = cont.valid; + if (cont.valid) { + this.checkOnGoing = true; + this.memberService + .listMembers(this.projectId, cont.value).toPromise() + .then((members: Member[]) => { + if (members.filter(m => { return m.username === cont.value }).length > 0) { + this.isMemberNameValid = false; + this.memberTooltip = 'MEMBER.USERNAME_ALREADY_EXISTS'; + } + this.checkOnGoing = false; + }) + .catch(error => { + this.checkOnGoing = false; + }); + } else { + this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; + } + } + }); + } + + ngOnDestroy(): void { + this.nameChecker.unsubscribe(); + } onSubmit(): void { - if(!this.member.username || this.member.username.length === 0) { return; } + if (!this.member.username || this.member.username.length === 0) { return; } this.memberService - .addMember(this.projectId, this.member.username, +this.member.role_id) - .subscribe( - response=>{ - this.messageHandlerService.showSuccess('MEMBER.ADDED_SUCCESS'); - this.added.emit(true); - this.addMemberOpened = false; - }, - error=>{ - if (error instanceof Response) { - let errorMessageKey: string; - switch(error.status){ - case 404: - errorMessageKey = 'MEMBER.USERNAME_DOES_NOT_EXISTS'; - break; - case 409: - errorMessageKey = 'MEMBER.USERNAME_ALREADY_EXISTS'; - break; - default: - errorMessageKey = 'MEMBER.UNKNOWN_ERROR'; - } - if(this.messageHandlerService.isAppLevel(error)) { - this.messageHandlerService.handleError(error); - this.addMemberOpened = false; - } else { - this.translateService - .get(errorMessageKey) - .subscribe(errorMessage=>this.inlineAlert.showInlineError(errorMessage)); - } - } + .addMember(this.projectId, this.member.username, +this.member.role_id) + .subscribe( + response => { + this.messageHandlerService.showSuccess('MEMBER.ADDED_SUCCESS'); + this.added.emit(true); + this.addMemberOpened = false; + }, + error => { + if (error instanceof Response) { + let errorMessageKey: string; + switch (error.status) { + case 404: + errorMessageKey = 'MEMBER.USERNAME_DOES_NOT_EXISTS'; + break; + case 409: + errorMessageKey = 'MEMBER.USERNAME_ALREADY_EXISTS'; + break; + default: + errorMessageKey = 'MEMBER.UNKNOWN_ERROR'; } - ); + if (this.messageHandlerService.isAppLevel(error)) { + this.messageHandlerService.handleError(error); + this.addMemberOpened = false; + } else { + this.translateService + .get(errorMessageKey) + .subscribe(errorMessage => this.inlineAlert.showInlineError(errorMessage)); + } + } + } + ); } onCancel() { - if(this.hasChanged) { - this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'}); + if (this.hasChanged) { + this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); } else { this.addMemberOpened = false; this.memberForm.reset(); @@ -102,19 +152,17 @@ export class AddMemberComponent implements AfterViewChecked { } ngAfterViewChecked(): void { - this.memberForm = this.currentForm; - if(this.memberForm) { - this.memberForm.valueChanges.subscribe(data=>{ - for(let i in data) { - let origin = this.initVal[i]; - let current = data[i]; - if(current && current !== origin) { - this.hasChanged = true; - break; - } else { - this.hasChanged = false; - this.inlineAlert.close(); - } + if (this.memberForm !== this.currentForm) { + this.memberForm = this.currentForm; + } + if (this.memberForm) { + this.memberForm.valueChanges.subscribe(data => { + let memberName = data['member_name']; + if (memberName && memberName !== '') { + this.hasChanged = true; + this.inlineAlert.close(); + } else { + this.hasChanged = false; } }); } @@ -132,6 +180,19 @@ export class AddMemberComponent implements AfterViewChecked { this.addMemberOpened = true; this.hasChanged = false; this.member.role_id = 1; + this.member.username = ''; + this.isMemberNameValid = true; + this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED'; } + handleValidation(): void { + let cont = this.currentForm.controls['member_name']; + if (cont) { + this.nameChecker.next(cont.value); + } + } + + public get isValid(): boolean { + return this.currentForm && this.currentForm.valid && this.isMemberNameValid; + } } \ No newline at end of file From 755654615e31f03247274c83fe9a9b6e38cc1027 Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Tue, 16 May 2017 18:23:09 -0700 Subject: [PATCH 6/7] Modify project name checking --- .../create-project.component.html | 4 +- .../create-project.component.ts | 86 ++++++++++++------- .../member/add-member/add-member.component.ts | 5 +- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html index e3a647962..abf057aa8 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.html +++ b/src/ui_ng/src/app/project/create-project/create-project.component.html @@ -13,9 +13,7 @@ pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" minlength="2" #projectName="ngModel" - (input)='handleValidation(false)' - (blur)='handleValidation(true)' - > + (keyup)='handleValidation()'> {{ nameTooltipText | translate }} diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index 774aa9d55..89635a905 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -11,7 +11,16 @@ // 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 { Component, EventEmitter, Output, ViewChild, AfterViewChecked, HostBinding } from '@angular/core'; +import { + Component, + EventEmitter, + Output, + ViewChild, + AfterViewChecked, + HostBinding, + OnInit, + OnDestroy +} from '@angular/core'; import { Response } from '@angular/http'; import { NgForm } from '@angular/forms'; @@ -23,12 +32,16 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com import { TranslateService } from '@ngx-translate/core'; +import { Subject } from 'rxjs/Subject'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; + @Component({ selector: 'create-project', templateUrl: 'create-project.component.html', styleUrls: ['create-project.css'] }) -export class CreateProjectComponent implements AfterViewChecked { +export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestroy { projectForm: NgForm; @@ -48,6 +61,7 @@ export class CreateProjectComponent implements AfterViewChecked { isNameValid: boolean = true; nameTooltipText: string = 'PROJECT.NAME_TOOLTIP'; checkOnGoing: boolean = false; + proNameChecker: Subject = new Subject(); @Output() create = new EventEmitter(); @ViewChild(InlineAlertComponent) @@ -61,6 +75,39 @@ export class CreateProjectComponent implements AfterViewChecked { return this.project.public ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE'; } + ngOnInit(): void { + this.proNameChecker + .debounceTime(500) + .distinctUntilChanged() + .subscribe((name: string) => { + let cont = this.currentForm.controls["create_project_name"]; + if (cont && this.hasChanged) { + this.isNameValid = cont.valid; + if (this.isNameValid) { + //Check exiting from backend + this.checkOnGoing = true; + this.projectService + .checkProjectExists(cont.value).toPromise() + .then(() => { + //Project existing + this.isNameValid = false; + this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS'; + this.checkOnGoing = false; + }) + .catch(error => { + this.checkOnGoing = false; + }); + } else { + this.nameTooltipText = 'PROJECT.NAME_TOOLTIP'; + } + } + }); + } + + ngOnDestroy(): void { + this.proNameChecker.unsubscribe(); + } + onSubmit() { this.projectService .createProject(this.project.name, this.project.public ? 1 : 0) @@ -135,36 +182,17 @@ export class CreateProjectComponent implements AfterViewChecked { } public get isValid(): boolean { - return this.currentForm && this.currentForm.valid && this.isNameValid; + return this.currentForm && + this.currentForm.valid && + this.isNameValid && + !this.checkOnGoing; } //Handle the form validation - handleValidation(flag: boolean): void { - if (flag) { - //validate - let cont = this.currentForm.controls["create_project_name"]; - if (cont) { - this.isNameValid = cont.valid; - if (this.isNameValid && this.hasChanged) { - //Check exiting from backend - this.checkOnGoing = true; - this.projectService - .checkProjectExists(cont.value).toPromise() - .then(() => { - //Project existing - this.isNameValid = false; - this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS'; - this.checkOnGoing = false; - }) - .catch(error => { - this.checkOnGoing = false; - }); - } - } - } else { - //reset - this.isNameValid = true; - this.nameTooltipText = 'PROJECT.NAME_TOOLTIP'; + handleValidation(): void { + let cont = this.currentForm.controls["create_project_name"]; + if (cont) { + this.proNameChecker.next(cont.value); } } } diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts index 77a2c51e7..335358f49 100644 --- a/src/ui_ng/src/app/project/member/add-member/add-member.component.ts +++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.ts @@ -193,6 +193,9 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy { } public get isValid(): boolean { - return this.currentForm && this.currentForm.valid && this.isMemberNameValid; + return this.currentForm && + this.currentForm.valid && + this.isMemberNameValid && + !this.checkOnGoing; } } \ No newline at end of file From 833c9f8f2ed31b1d9242517c52d1f2c3fb70d158 Mon Sep 17 00:00:00 2001 From: kunw Date: Wed, 17 May 2017 13:33:31 +0800 Subject: [PATCH 7/7] Refine spec of repository. --- src/ui_ng/lib/src/repository/repository.component.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui_ng/lib/src/repository/repository.component.spec.ts b/src/ui_ng/lib/src/repository/repository.component.spec.ts index 8793c6531..5b2a6c551 100644 --- a/src/ui_ng/lib/src/repository/repository.component.spec.ts +++ b/src/ui_ng/lib/src/repository/repository.component.spec.ts @@ -96,13 +96,14 @@ describe('RepositoryComponent (inline template)', ()=> { fixture.detectChanges(); comp.doSearchRepoNames('nginx'); fixture.detectChanges(); - let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell')); + let de: DebugElement[] = fixture.debugElement.queryAll(By.css('datagrid-cell')); fixture.detectChanges(); expect(de).toBeTruthy(); - let el: HTMLElement = de.nativeElement; + expect(de.length).toEqual(1); + let el: HTMLElement = de[0].nativeElement; + fixture.detectChanges(); expect(el).toBeTruthy(); expect(el.textContent).toEqual('library/nginx'); - expect(el.textContent).not.toEqual('library/busybox'); }); }));