mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-01 04:21:36 +01:00
Merge pull request #1361 from vmware/feature/sign_in_migration
Implement user sign-in component with clarity and Angular2
This commit is contained in:
commit
d689784d7f
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
coverage/
|
||||||
|
dist/
|
||||||
|
html-report/
|
||||||
|
node_modules/
|
||||||
|
typings/
|
||||||
|
**/*npm-debug.log.*
|
||||||
|
**/*yarn-error.log.*
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
proxy.config.json
|
||||||
|
*.log
|
||||||
|
**/*.log
|
@ -1,58 +1,58 @@
|
|||||||
{
|
{
|
||||||
"name": "clarity-seed",
|
"name": "clarity-seed",
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"description": "Angular-CLI starter for a Clarity project",
|
"description": "Angular-CLI starter for a Clarity project",
|
||||||
"angular-cli": {},
|
"angular-cli": {},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ng serve --host 0.0.0.0",
|
"start": "ng serve --host 0.0.0.0 --proxy-config proxy.config.json",
|
||||||
"lint": "tslint \"src/**/*.ts\"",
|
"lint": "tslint \"src/**/*.ts\"",
|
||||||
"test": "ng test --single-run",
|
"test": "ng test --single-run",
|
||||||
"pree2e": "webdriver-manager update",
|
"pree2e": "webdriver-manager update",
|
||||||
"e2e": "protractor"
|
"e2e": "protractor"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "^2.4.1",
|
"@angular/common": "^2.4.1",
|
||||||
"@angular/compiler": "^2.4.1",
|
"@angular/compiler": "^2.4.1",
|
||||||
"@angular/core": "^2.4.1",
|
"@angular/core": "^2.4.1",
|
||||||
"@angular/forms": "^2.4.1",
|
"@angular/forms": "^2.4.1",
|
||||||
"@angular/http": "^2.4.1",
|
"@angular/http": "^2.4.1",
|
||||||
"@angular/platform-browser": "^2.4.1",
|
"@angular/platform-browser": "^2.4.1",
|
||||||
"@angular/platform-browser-dynamic": "^2.4.1",
|
"@angular/platform-browser-dynamic": "^2.4.1",
|
||||||
"@angular/router": "^3.4.1",
|
"@angular/router": "^3.4.1",
|
||||||
"@webcomponents/custom-elements": "1.0.0-alpha.3",
|
"@webcomponents/custom-elements": "1.0.0-alpha.3",
|
||||||
"clarity-angular": "^0.8.0",
|
"clarity-angular": "^0.8.0",
|
||||||
"clarity-icons": "^0.8.0",
|
"clarity-icons": "^0.8.0",
|
||||||
"clarity-ui": "^0.8.0",
|
"clarity-ui": "^0.8.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"mutationobserver-shim": "^0.3.2",
|
"mutationobserver-shim": "^0.3.2",
|
||||||
"rxjs": "^5.0.1",
|
"rxjs": "^5.0.1",
|
||||||
"ts-helpers": "^1.1.1",
|
"ts-helpers": "^1.1.1",
|
||||||
"web-animations-js": "^2.2.1",
|
"web-animations-js": "^2.2.1",
|
||||||
"zone.js": "^0.7.2"
|
"zone.js": "^0.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^2.4.1",
|
"@angular/compiler-cli": "^2.4.1",
|
||||||
"@types/core-js": "^0.9.34",
|
"@types/core-js": "^0.9.34",
|
||||||
"@types/jasmine": "^2.2.30",
|
"@types/jasmine": "^2.2.30",
|
||||||
"@types/node": "^6.0.42",
|
"@types/node": "^6.0.42",
|
||||||
"angular-cli": "^1.0.0-beta.24",
|
"angular-cli": "^1.0.0-beta.24",
|
||||||
"bootstrap": "4.0.0-alpha.5",
|
"bootstrap": "4.0.0-alpha.5",
|
||||||
"codelyzer": "~1.0.0-beta.3",
|
"codelyzer": "~1.0.0-beta.3",
|
||||||
"enhanced-resolve": "^3.0.0",
|
"enhanced-resolve": "^3.0.0",
|
||||||
"jasmine-core": "2.4.1",
|
"jasmine-core": "2.4.1",
|
||||||
"jasmine-spec-reporter": "2.5.0",
|
"jasmine-spec-reporter": "2.5.0",
|
||||||
"karma": "1.2.0",
|
"karma": "1.2.0",
|
||||||
"karma-cli": "^1.0.1",
|
"karma-cli": "^1.0.1",
|
||||||
"karma-jasmine": "^1.0.2",
|
"karma-jasmine": "^1.0.2",
|
||||||
"karma-mocha-reporter": "^2.2.1",
|
"karma-mocha-reporter": "^2.2.1",
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-remap-istanbul": "^0.2.1",
|
"karma-remap-istanbul": "^0.2.1",
|
||||||
"protractor": "4.0.9",
|
"protractor": "4.0.9",
|
||||||
"ts-node": "1.2.1",
|
"ts-node": "1.2.1",
|
||||||
"tslint": "^4.1.1",
|
"tslint": "^4.1.1",
|
||||||
"typescript": "~2.0.3",
|
"typescript": "~2.0.3",
|
||||||
"typings": "^1.4.0",
|
"typings": "^1.4.0",
|
||||||
"webdriver-manager": "10.2.5"
|
"webdriver-manager": "10.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { SignInComponent } from './sign-in.component';
|
import { SignInComponent } from './sign-in/sign-in.component';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
<div class="login-wrapper">
|
|
||||||
<form class="login">
|
|
||||||
<label class="title">
|
|
||||||
VMware Harbor<span class="trademark">™</span>
|
|
||||||
</label>
|
|
||||||
<div class="login-group">
|
|
||||||
<div class="auth-source select">
|
|
||||||
<select id="login-auth-source-1">
|
|
||||||
<option>Local Users</option>
|
|
||||||
<option>Administrator</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<input class="username" type="text" id="login_username" placeholder="Username">
|
|
||||||
<input class="password" type="password" id="login_password" placeholder="Password">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input type="checkbox" id="rememberme">
|
|
||||||
<label for="rememberme">
|
|
||||||
Remember me
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="error active">
|
|
||||||
Invalid user name or password
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" [routerLink]="['/harbor', 'dashboard']">LOG IN</button>
|
|
||||||
<a href="..." class="signup">Sign up for an account</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'sign-in',
|
|
||||||
templateUrl: "sign-in.component.html"
|
|
||||||
})
|
|
||||||
export class SignInComponent {
|
|
||||||
// constructor(private router: Router){}
|
|
||||||
}
|
|
15
harbor-app/src/app/account/sign-in/sign-in-credential.ts
Normal file
15
harbor-app/src/app/account/sign-in/sign-in-credential.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Declare class for store the sign in data,
|
||||||
|
* two prperties:
|
||||||
|
* principal: The username used to sign in
|
||||||
|
* password: The password used to sign in
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class SignInCredential
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SignInCredential {
|
||||||
|
principal: string;
|
||||||
|
password: string;
|
||||||
|
}
|
7
harbor-app/src/app/account/sign-in/sign-in.component.css
Normal file
7
harbor-app/src/app/account/sign-in/sign-in.component.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.progress-size-small {
|
||||||
|
height: 0.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
39
harbor-app/src/app/account/sign-in/sign-in.component.html
Normal file
39
harbor-app/src/app/account/sign-in/sign-in.component.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<div class="login-wrapper">
|
||||||
|
<form #signInForm="ngForm" class="login">
|
||||||
|
<label class="title">
|
||||||
|
VMware Harbor<span class="trademark">™</span>
|
||||||
|
</label>
|
||||||
|
<div class="login-group">
|
||||||
|
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="userNameInput.invalid && (userNameInput.dirty || userNameInput.touched)">
|
||||||
|
<input class="username" type="text" required
|
||||||
|
[(ngModel)]="signInCredential.principal"
|
||||||
|
name="login_username" id="login_username" placeholder="Username"
|
||||||
|
#userNameInput='ngModel'>
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Username is required!
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||||
|
<input class="password" type="password" required
|
||||||
|
[(ngModel)]="signInCredential.password"
|
||||||
|
name="login_password" id="login_password" placeholder="Password"
|
||||||
|
#passwordInput="ngModel">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Password is required!
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" id="rememberme">
|
||||||
|
<label for="rememberme">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
|
||||||
|
Invalid user name or password
|
||||||
|
</div>
|
||||||
|
<button [class.visibility-hidden]="signInStatus === statusOnGoing" [disabled]="signInStatus === statusOnGoing" type="submit" class="btn btn-primary" (click)="signIn()">LOG IN</button>
|
||||||
|
<div [class.visibility-hidden]="signInStatus != statusOnGoing" class="progress loop progress-size-small"><progress></progress></div>
|
||||||
|
<a href="javascript:void(0)" class="signup" (click)="signUp()">Sign up for an account</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
121
harbor-app/src/app/account/sign-in/sign-in.component.ts
Normal file
121
harbor-app/src/app/account/sign-in/sign-in.component.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Input, ViewChild, AfterViewChecked } from '@angular/core';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
|
import { SignInService } from './sign-in.service';
|
||||||
|
import { SignInCredential } from './sign-in-credential'
|
||||||
|
|
||||||
|
//Define status flags for signing in states
|
||||||
|
export const signInStatusNormal = 0;
|
||||||
|
export const signInStatusOnGoing = 1;
|
||||||
|
export const signInStatusError = -1;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'sign-in',
|
||||||
|
templateUrl: "sign-in.component.html",
|
||||||
|
styleUrls: ['sign-in.component.css'],
|
||||||
|
|
||||||
|
providers: [SignInService]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class SignInComponent implements AfterViewChecked {
|
||||||
|
//Form reference
|
||||||
|
signInForm: NgForm;
|
||||||
|
@ViewChild('signInForm') currentForm: NgForm;
|
||||||
|
|
||||||
|
//Status flag
|
||||||
|
signInStatus: number = 0;
|
||||||
|
|
||||||
|
//Initialize sign in credential
|
||||||
|
@Input() signInCredential: SignInCredential = {
|
||||||
|
principal: "",
|
||||||
|
password: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private signInService: SignInService,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
//For template accessing
|
||||||
|
get statusError(): number {
|
||||||
|
return signInStatusError;
|
||||||
|
}
|
||||||
|
|
||||||
|
get statusOnGoing(): number {
|
||||||
|
return signInStatusOnGoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate the related fields
|
||||||
|
private validate(): boolean {
|
||||||
|
return true;
|
||||||
|
//return this.signInForm.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Hande form values changes
|
||||||
|
private formChanged() {
|
||||||
|
if (this.currentForm === this.signInForm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.signInForm = this.currentForm;
|
||||||
|
if (this.signInForm) {
|
||||||
|
this.signInForm.valueChanges
|
||||||
|
.subscribe(data => {
|
||||||
|
this.updateState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Implement interface
|
||||||
|
//Watch the view change only when view is in error state
|
||||||
|
ngAfterViewChecked() {
|
||||||
|
if (this.signInStatus === signInStatusError) {
|
||||||
|
this.formChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the status if we have done some changes
|
||||||
|
updateState(): void {
|
||||||
|
if (this.signInStatus === signInStatusError) {
|
||||||
|
this.signInStatus = signInStatusNormal; //reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Trigger the signin action
|
||||||
|
signIn(): void {
|
||||||
|
//Should validate input firstly
|
||||||
|
if (!this.validate()) {
|
||||||
|
console.info("return");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start signing in progress
|
||||||
|
this.signInStatus = signInStatusOnGoing;
|
||||||
|
|
||||||
|
//Call the service to send out the http request
|
||||||
|
this.signInService.signIn(this.signInCredential)
|
||||||
|
.then(() => {
|
||||||
|
//Set status
|
||||||
|
this.signInStatus = signInStatusNormal;
|
||||||
|
|
||||||
|
//Routing to the right location
|
||||||
|
let nextRoute = ["/harbor", "dashboard"];
|
||||||
|
this.router.navigate(nextRoute);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
//Set error status
|
||||||
|
this.signInStatus = signInStatusError;
|
||||||
|
|
||||||
|
let message = error.status ? error.status + ":" + error.statusText : error;
|
||||||
|
console.error("An error occurred when signing in:", message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Help user navigate to the sign up
|
||||||
|
signUp(): void {
|
||||||
|
let nextRoute = ["/harbor", "signup"];
|
||||||
|
this.router.navigate(nextRoute);
|
||||||
|
}
|
||||||
|
}
|
41
harbor-app/src/app/account/sign-in/sign-in.service.ts
Normal file
41
harbor-app/src/app/account/sign-in/sign-in.service.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Headers, Http, URLSearchParams } from '@angular/http';
|
||||||
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
|
||||||
|
import { SignInCredential } from './sign-in-credential';
|
||||||
|
|
||||||
|
const signInUrl = '/login';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Define a service to provide sign in methods
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class SignInService
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class SignInService {
|
||||||
|
private headers = new Headers({
|
||||||
|
"Content-Type": 'application/x-www-form-urlencoded'
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(private http: Http) {}
|
||||||
|
|
||||||
|
//Handle the related exceptions
|
||||||
|
private handleError(error: any): Promise<any>{
|
||||||
|
return Promise.reject(error.message || error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Submit signin form to backend (NOT restful service)
|
||||||
|
signIn(signInCredential: SignInCredential): Promise<any>{
|
||||||
|
//Build the form package
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
body.set('principal', signInCredential.principal);
|
||||||
|
body.set('password', signInCredential.password);
|
||||||
|
|
||||||
|
//Trigger Http
|
||||||
|
return this.http.post(signInUrl, body.toString(), { headers: this.headers })
|
||||||
|
.toPromise()
|
||||||
|
.then(()=>null)
|
||||||
|
.catch(this.handleError);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<p>Placeholder for signup</p>
|
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { SignInComponent } from './account/sign-in.component';
|
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||||
|
|
||||||
const harborRoutes: Routes = [
|
const harborRoutes: Routes = [
|
||||||
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },
|
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },
|
||||||
|
Loading…
Reference in New Issue
Block a user