mirror of
https://github.com/goharbor/harbor
synced 2024-09-21 07:46:39 +00:00
Merge remote-tracking branch 'upstream/master' into master-replication
This commit is contained in:
commit
45ef8adca2
|
@ -36,6 +36,10 @@ const metaChars = "&|!=~*<>()"
|
||||||
func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||||
|
|
||||||
p := m.Principal
|
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 {
|
for _, c := range metaChars {
|
||||||
if strings.ContainsRune(p, c) {
|
if strings.ContainsRune(p, c) {
|
||||||
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
return nil, fmt.Errorf("the principal contains meta char: %q", c)
|
||||||
|
|
|
@ -131,4 +131,13 @@ func TestAuthenticate(t *testing.T) {
|
||||||
if user != nil {
|
if user != nil {
|
||||||
t.Errorf("Nil user expected for wrong password")
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,13 +96,14 @@ describe('RepositoryComponent (inline template)', ()=> {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
comp.doSearchRepoNames('nginx');
|
comp.doSearchRepoNames('nginx');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell'));
|
let de: DebugElement[] = fixture.debugElement.queryAll(By.css('datagrid-cell'));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(de).toBeTruthy();
|
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).toBeTruthy();
|
||||||
expect(el.textContent).toEqual('library/nginx');
|
expect(el.textContent).toEqual('library/nginx');
|
||||||
expect(el.textContent).not.toEqual('library/busybox');
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
|
||||||
if (this.searchSub) {
|
if (this.searchSub) {
|
||||||
this.searchSub.unsubscribe();
|
this.searchSub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.closeSub) {
|
||||||
|
this.closeSub.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Handle the term inputting event
|
//Handle the term inputting event
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
<clr-modal [(clrModalOpen)]="createProjectOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
<clr-modal [(clrModalOpen)]="createProjectOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
<h3 class="modal-title">{{'PROJECT.NEW_PROJECT' | translate}}</h3>
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||||
<div class="modal-body" style="height: 12.8em; overflow-y: hidden;">
|
<div class="modal-body" style="height: 16.8em; overflow-y: hidden;">
|
||||||
<form #projectForm="ngForm">
|
<form #projectForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group" style="padding-left: 135px;">
|
<div class="form-group" style="padding-left: 135px;">
|
||||||
<label for="create_project_name" class="col-md-3 form-group-label-override">{{'PROJECT.NAME' | translate}}</label>
|
<label for="create_project_name" class="col-md-3 form-group-label-override required">{{'PROJECT.NAME' | translate}}</label>
|
||||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isNameValid" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left">
|
||||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="38" required minlength="2" #projectName="ngModel" targetExists="PROJECT_NAME">
|
<input type="text" id="create_project_name" [(ngModel)]="project.name"
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
name="create_project_name" size="36"
|
||||||
{{'PROJECT.NAME_IS_REQUIRED' | translate}}
|
required
|
||||||
</span>
|
pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.minlength && (projectName.dirty || projectName.touched)">
|
minlength="2"
|
||||||
{{'PROJECT.NAME_MINIMUM_LENGTH' | translate}}
|
#projectName="ngModel"
|
||||||
</span>
|
(keyup)='handleValidation()'>
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.targetExists && (projectName.dirty || projectName.touched)">
|
<span class="tooltip-content">
|
||||||
{{'PROJECT.NAME_ALREADY_EXISTS' | translate}}
|
{{ nameTooltipText | translate }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group" style="padding-left: 135px;">
|
||||||
<label class="col-md-4 form-group-label-override">{{'PROJECT.PUBLIC' | translate}}</label>
|
<label class="col-md-4 form-group-label-override">{{'PROJECT.ACCESS_LEVEL' | translate}}</label>
|
||||||
<div class="checkbox-inline">
|
<div class="checkbox-inline">
|
||||||
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
||||||
<label for="create_project_public"></label>
|
<label for="create_project_public"></label>
|
||||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-right" style="top:-8px; left:-8px;">
|
<span class="access-level-label">{{ accessLevelDisplayText | translate}}</span>
|
||||||
|
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-md tooltip-bottom-right" style="top:-8px; left:-8px;">
|
||||||
<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>
|
||||||
<span class="tooltip-content" style="margin-left: 5px;">{{'PROJECT.INLINE_HELP_PUBLIC' | translate }}</span>
|
<span class="tooltip-content" style="margin-left: 5px;">{{'PROJECT.INLINE_HELP_PUBLIC' | translate }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -35,6 +37,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
<button type="button" class="btn btn-primary" [disabled]="projectForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
<button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
|
@ -11,7 +11,16 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { Response } from '@angular/http';
|
||||||
import { NgForm } from '@angular/forms';
|
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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import 'rxjs/add/operator/debounceTime';
|
||||||
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'create-project',
|
selector: 'create-project',
|
||||||
templateUrl: 'create-project.component.html',
|
templateUrl: 'create-project.component.html',
|
||||||
styleUrls: ['create-project.css']
|
styleUrls: ['create-project.css']
|
||||||
})
|
})
|
||||||
export class CreateProjectComponent implements AfterViewChecked {
|
export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||||
|
|
||||||
projectForm: NgForm;
|
projectForm: NgForm;
|
||||||
|
|
||||||
|
@ -45,6 +58,11 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
staticBackdrop: boolean = true;
|
staticBackdrop: boolean = true;
|
||||||
closable: boolean = false;
|
closable: boolean = false;
|
||||||
|
|
||||||
|
isNameValid: boolean = true;
|
||||||
|
nameTooltipText: string = 'PROJECT.NAME_TOOLTIP';
|
||||||
|
checkOnGoing: boolean = false;
|
||||||
|
proNameChecker: Subject<string> = new Subject<string>();
|
||||||
|
|
||||||
@Output() create = new EventEmitter<boolean>();
|
@Output() create = new EventEmitter<boolean>();
|
||||||
@ViewChild(InlineAlertComponent)
|
@ViewChild(InlineAlertComponent)
|
||||||
inlineAlert: InlineAlertComponent;
|
inlineAlert: InlineAlertComponent;
|
||||||
|
@ -53,6 +71,43 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private messageHandlerService: MessageHandlerService) { }
|
private messageHandlerService: MessageHandlerService) { }
|
||||||
|
|
||||||
|
public get accessLevelDisplayText(): string {
|
||||||
|
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() {
|
onSubmit() {
|
||||||
this.projectService
|
this.projectService
|
||||||
.createProject(this.project.name, this.project.public ? 1 : 0)
|
.createProject(this.project.name, this.project.public ? 1 : 0)
|
||||||
|
@ -125,5 +180,20 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
this.projectForm.reset();
|
this.projectForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
return this.currentForm &&
|
||||||
|
this.currentForm.valid &&
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,3 +2,12 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.access-level-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: -4px;
|
||||||
|
margin-right: 12px;
|
||||||
|
top: -6px;
|
||||||
|
position: relative;
|
||||||
|
}
|
|
@ -5,29 +5,32 @@
|
||||||
<form #memberForm="ngForm">
|
<form #memberForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="member_name" class="col-md-4 form-group-label-override">{{'MEMBER.NAME' | translate}}</label>
|
<label for="member_name" class="col-md-4 form-group-label-override required">{{'MEMBER.NAME' | translate}}</label>
|
||||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="memberName.invalid && (memberName.dirty || memberName.touched)" [class.valid]="memberName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left">
|
||||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" #memberName="ngModel" required targetExists="MEMBER_NAME" [projectId]="projectId">
|
<input type="text" id="member_name" [(ngModel)]="member.username"
|
||||||
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.required && (memberName.dirty || memberName.touched)">
|
name="member_name"
|
||||||
{{ 'MEMBER.USERNAME_IS_REQUIRED' | translate }}
|
size="20"
|
||||||
</span>
|
#memberName="ngModel"
|
||||||
<span class="tooltip-content" *ngIf="memberName.errors && memberName.errors.targetExists && (memberName.dirty || memberName.touched)">
|
required
|
||||||
{{ 'MEMBER.USERNAME_ALREADY_EXISTS' | translate }}
|
(keyup)='handleValidation()'>
|
||||||
|
<span class="tooltip-content">
|
||||||
|
{{ memberTooltip | translate }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" [value]="1" [(ngModel)]="member.role_id">
|
<input type="radio" name="member_role" id="checkrads_project_admin" [value]="1" [(ngModel)]="member.role_id">
|
||||||
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<input type="radio" name="roleRadios" id="checkrads_developer" [value]="2" [(ngModel)]="member.role_id">
|
<input type="radio" name="member_role" id="checkrads_developer" [value]="2" [(ngModel)]="member.role_id">
|
||||||
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>
|
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<input type="radio" name="roleRadios" id="checkrads_guest" [value]="3" [(ngModel)]="member.role_id">
|
<input type="radio" name="member_role" id="checkrads_guest" [value]="3" [(ngModel)]="member.role_id">
|
||||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +39,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
<button type="button" class="btn btn-primary" [disabled]="memberForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
<button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
|
@ -11,7 +11,16 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { Response } from '@angular/http';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
|
@ -24,15 +33,18 @@ import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { Member } from '../member';
|
import { Member } from '../member';
|
||||||
|
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import 'rxjs/add/operator/debounceTime';
|
||||||
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'add-member',
|
selector: 'add-member',
|
||||||
templateUrl: 'add-member.component.html',
|
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();
|
member: Member = new Member();
|
||||||
initVal: Member = new Member();
|
|
||||||
|
|
||||||
addMemberOpened: boolean;
|
addMemberOpened: boolean;
|
||||||
|
|
||||||
|
@ -52,10 +64,48 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Output() added = new EventEmitter<boolean>();
|
@Output() added = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
isMemberNameValid: boolean = true;
|
||||||
|
memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||||
|
nameChecker: Subject<string> = new Subject<string>();
|
||||||
|
checkOnGoing: boolean = false;
|
||||||
|
|
||||||
constructor(private memberService: MemberService,
|
constructor(private memberService: MemberService,
|
||||||
private messageHandlerService: MessageHandlerService,
|
private messageHandlerService: MessageHandlerService,
|
||||||
private translateService: TranslateService) { }
|
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 {
|
onSubmit(): void {
|
||||||
if (!this.member.username || this.member.username.length === 0) { return; }
|
if (!this.member.username || this.member.username.length === 0) { return; }
|
||||||
this.memberService
|
this.memberService
|
||||||
|
@ -102,19 +152,17 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
|
if (this.memberForm !== this.currentForm) {
|
||||||
this.memberForm = this.currentForm;
|
this.memberForm = this.currentForm;
|
||||||
|
}
|
||||||
if (this.memberForm) {
|
if (this.memberForm) {
|
||||||
this.memberForm.valueChanges.subscribe(data => {
|
this.memberForm.valueChanges.subscribe(data => {
|
||||||
for(let i in data) {
|
let memberName = data['member_name'];
|
||||||
let origin = this.initVal[i];
|
if (memberName && memberName !== '') {
|
||||||
let current = data[i];
|
|
||||||
if(current && current !== origin) {
|
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
this.inlineAlert.close();
|
||||||
} else {
|
} else {
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.inlineAlert.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -132,6 +180,22 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
this.addMemberOpened = true;
|
this.addMemberOpened = true;
|
||||||
this.hasChanged = false;
|
this.hasChanged = false;
|
||||||
this.member.role_id = 1;
|
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 &&
|
||||||
|
!this.checkOnGoing;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -39,7 +39,7 @@
|
||||||
"FULL_NAME": "Maximum length should be 20 characters.",
|
"FULL_NAME": "Maximum length should be 20 characters.",
|
||||||
"COMMENT": "Length of comment should be less than 20 characters.",
|
"COMMENT": "Length of comment should be less than 20 characters.",
|
||||||
"CURRENT_PWD": "Current password is required.",
|
"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.",
|
"CONFIRM_PWD": "Passwords do not match.",
|
||||||
"SIGN_IN_USERNAME": "Username is required.",
|
"SIGN_IN_USERNAME": "Username is required.",
|
||||||
"SIGN_IN_PWD": "Password is required.",
|
"SIGN_IN_PWD": "Password is required.",
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"NEW_PWD": "New Password",
|
"NEW_PWD": "New Password",
|
||||||
"CONFIRM_PWD": "Confirm Password",
|
"CONFIRM_PWD": "Confirm Password",
|
||||||
"SAVE_SUCCESS": "User password changed successfully.",
|
"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": {
|
"ACCOUNT_SETTINGS": {
|
||||||
"PROFILE": "User Profile",
|
"PROFILE": "User Profile",
|
||||||
|
@ -126,6 +126,7 @@
|
||||||
"PUBLIC_OR_PRIVATE": "Access Level",
|
"PUBLIC_OR_PRIVATE": "Access Level",
|
||||||
"REPO_COUNT": "Repositories Count",
|
"REPO_COUNT": "Repositories Count",
|
||||||
"CREATION_TIME": "Creation Time",
|
"CREATION_TIME": "Creation Time",
|
||||||
|
"ACCESS_LEVEL": "Access Level",
|
||||||
"PUBLIC": "Public",
|
"PUBLIC": "Public",
|
||||||
"PRIVATE": "Private",
|
"PRIVATE": "Private",
|
||||||
"MAKE": "Make",
|
"MAKE": "Make",
|
||||||
|
@ -135,6 +136,7 @@
|
||||||
"PUBLIC_PROJECTS": "Public Projects",
|
"PUBLIC_PROJECTS": "Public Projects",
|
||||||
"PROJECT": "Project",
|
"PROJECT": "Project",
|
||||||
"NEW_PROJECT": "New 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_IS_REQUIRED": "Project name is required.",
|
||||||
"NAME_MINIMUM_LENGTH": "Project name is too short, it should be greater than 2 characters.",
|
"NAME_MINIMUM_LENGTH": "Project name is too short, it should be greater than 2 characters.",
|
||||||
"NAME_ALREADY_EXISTS": "Project name already exists.",
|
"NAME_ALREADY_EXISTS": "Project name already exists.",
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"FULL_NAME": "La longitud máxima debería ser de 20 caracteres.",
|
"FULL_NAME": "La longitud máxima debería ser de 20 caracteres.",
|
||||||
"COMMENT": "La longitud del comentario debería ser menor de 20 caracteres.",
|
"COMMENT": "La longitud del comentario debería ser menor de 20 caracteres.",
|
||||||
"CURRENT_PWD": "Es obligatorio introducir la contraseña actual.",
|
"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.",
|
"CONFIRM_PWD": "Las contraseñas no coinciden.",
|
||||||
"SIGN_IN_USERNAME": "El nombre de usuario es obligatorio.",
|
"SIGN_IN_USERNAME": "El nombre de usuario es obligatorio.",
|
||||||
"SIGN_IN_PWD": "La contraseña es obligatoria.",
|
"SIGN_IN_PWD": "La contraseña es obligatoria.",
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"NEW_PWD": "Nueva contraseña",
|
"NEW_PWD": "Nueva contraseña",
|
||||||
"CONFIRM_PWD": "Confirmar contraseña",
|
"CONFIRM_PWD": "Confirmar contraseña",
|
||||||
"SAVE_SUCCESS": "Contraseña de usuario guardada satisfactoriamente.",
|
"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": {
|
"ACCOUNT_SETTINGS": {
|
||||||
"PROFILE": "Perfil de usuario",
|
"PROFILE": "Perfil de usuario",
|
||||||
|
@ -136,6 +136,7 @@
|
||||||
"PUBLIC_PROJECTS": "Proyectos Públicos",
|
"PUBLIC_PROJECTS": "Proyectos Públicos",
|
||||||
"PROJECT": "Proyecto",
|
"PROJECT": "Proyecto",
|
||||||
"NEW_PROJECT": "Nuevo 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_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_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.",
|
"NAME_ALREADY_EXISTS": "Ya existe un proyecto con ese nombre.",
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"FULL_NAME": "长度不能超过20。",
|
"FULL_NAME": "长度不能超过20。",
|
||||||
"COMMENT": "长度不能超过20。",
|
"COMMENT": "长度不能超过20。",
|
||||||
"CURRENT_PWD": "当前密码为必填项。",
|
"CURRENT_PWD": "当前密码为必填项。",
|
||||||
"PASSWORD": "密码长度至少为8且需包含至少一个大写字符,一个小写字符和一个数字。",
|
"PASSWORD": "密码长度在8到20之间且需包含至少一个大写字符,一个小写字符和一个数字。",
|
||||||
"CONFIRM_PWD": "密码输入不一致。",
|
"CONFIRM_PWD": "密码输入不一致。",
|
||||||
"SIGN_IN_USERNAME": "用户名为必填项。",
|
"SIGN_IN_USERNAME": "用户名为必填项。",
|
||||||
"SIGN_IN_PWD": "密码为必填项。",
|
"SIGN_IN_PWD": "密码为必填项。",
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"NEW_PWD": "新密码",
|
"NEW_PWD": "新密码",
|
||||||
"CONFIRM_PWD": "确认密码",
|
"CONFIRM_PWD": "确认密码",
|
||||||
"SAVE_SUCCESS": "成功更改用户密码。",
|
"SAVE_SUCCESS": "成功更改用户密码。",
|
||||||
"PASS_TIPS": "至少8个字符且需包含至少一个大写字符、小写字符或者数字"
|
"PASS_TIPS": "8到20个字符且需包含至少一个大写字符、小写字符或者数字"
|
||||||
},
|
},
|
||||||
"ACCOUNT_SETTINGS": {
|
"ACCOUNT_SETTINGS": {
|
||||||
"PROFILE": "用户设置",
|
"PROFILE": "用户设置",
|
||||||
|
@ -126,6 +126,7 @@
|
||||||
"PUBLIC_OR_PRIVATE": "访问级别",
|
"PUBLIC_OR_PRIVATE": "访问级别",
|
||||||
"REPO_COUNT": "镜像仓库数",
|
"REPO_COUNT": "镜像仓库数",
|
||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
|
"ACCESS_LEVEL": "访问级别",
|
||||||
"PUBLIC": "公开",
|
"PUBLIC": "公开",
|
||||||
"PRIVATE": "私有",
|
"PRIVATE": "私有",
|
||||||
"MAKE": "设为",
|
"MAKE": "设为",
|
||||||
|
@ -135,7 +136,8 @@
|
||||||
"PUBLIC_PROJECTS": "公开项目",
|
"PUBLIC_PROJECTS": "公开项目",
|
||||||
"PROJECT": "项目",
|
"PROJECT": "项目",
|
||||||
"NEW_PROJECT": "新建项目",
|
"NEW_PROJECT": "新建项目",
|
||||||
"NAME_IS_REQUIRED": "项目名称为必填项",
|
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||||
|
"NAME_IS_REQUIRED": "项目名称为必填项。",
|
||||||
"NAME_MINIMUM_LENGTH": "项目名称长度过短,至少多于2个字符。",
|
"NAME_MINIMUM_LENGTH": "项目名称长度过短,至少多于2个字符。",
|
||||||
"NAME_ALREADY_EXISTS": "项目名称已存在。",
|
"NAME_ALREADY_EXISTS": "项目名称已存在。",
|
||||||
"NAME_IS_ILLEGAL": "项目名称非法。",
|
"NAME_IS_ILLEGAL": "项目名称非法。",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user