diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..2eab1ad71
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/harbor-app/package.json b/harbor-app/package.json
index dc6631441..cf7068e87 100644
--- a/harbor-app/package.json
+++ b/harbor-app/package.json
@@ -1,58 +1,58 @@
{
- "name": "clarity-seed",
- "version": "0.8.0",
- "description": "Angular-CLI starter for a Clarity project",
- "angular-cli": {},
- "scripts": {
- "start": "ng serve --host 0.0.0.0",
- "lint": "tslint \"src/**/*.ts\"",
- "test": "ng test --single-run",
- "pree2e": "webdriver-manager update",
- "e2e": "protractor"
- },
- "private": true,
- "dependencies": {
- "@angular/common": "^2.4.1",
- "@angular/compiler": "^2.4.1",
- "@angular/core": "^2.4.1",
- "@angular/forms": "^2.4.1",
- "@angular/http": "^2.4.1",
- "@angular/platform-browser": "^2.4.1",
- "@angular/platform-browser-dynamic": "^2.4.1",
- "@angular/router": "^3.4.1",
- "@webcomponents/custom-elements": "1.0.0-alpha.3",
- "clarity-angular": "^0.8.0",
- "clarity-icons": "^0.8.0",
- "clarity-ui": "^0.8.0",
- "core-js": "^2.4.1",
- "mutationobserver-shim": "^0.3.2",
- "rxjs": "^5.0.1",
- "ts-helpers": "^1.1.1",
- "web-animations-js": "^2.2.1",
- "zone.js": "^0.7.2"
- },
- "devDependencies": {
- "@angular/compiler-cli": "^2.4.1",
- "@types/core-js": "^0.9.34",
- "@types/jasmine": "^2.2.30",
- "@types/node": "^6.0.42",
- "angular-cli": "^1.0.0-beta.24",
- "bootstrap": "4.0.0-alpha.5",
- "codelyzer": "~1.0.0-beta.3",
- "enhanced-resolve": "^3.0.0",
- "jasmine-core": "2.4.1",
- "jasmine-spec-reporter": "2.5.0",
- "karma": "1.2.0",
- "karma-cli": "^1.0.1",
- "karma-jasmine": "^1.0.2",
- "karma-mocha-reporter": "^2.2.1",
- "karma-phantomjs-launcher": "^1.0.0",
- "karma-remap-istanbul": "^0.2.1",
- "protractor": "4.0.9",
- "ts-node": "1.2.1",
- "tslint": "^4.1.1",
- "typescript": "~2.0.3",
- "typings": "^1.4.0",
- "webdriver-manager": "10.2.5"
- }
-}
+ "name": "clarity-seed",
+ "version": "0.8.0",
+ "description": "Angular-CLI starter for a Clarity project",
+ "angular-cli": {},
+ "scripts": {
+ "start": "ng serve --host 0.0.0.0 --proxy-config proxy.config.json",
+ "lint": "tslint \"src/**/*.ts\"",
+ "test": "ng test --single-run",
+ "pree2e": "webdriver-manager update",
+ "e2e": "protractor"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/common": "^2.4.1",
+ "@angular/compiler": "^2.4.1",
+ "@angular/core": "^2.4.1",
+ "@angular/forms": "^2.4.1",
+ "@angular/http": "^2.4.1",
+ "@angular/platform-browser": "^2.4.1",
+ "@angular/platform-browser-dynamic": "^2.4.1",
+ "@angular/router": "^3.4.1",
+ "@webcomponents/custom-elements": "1.0.0-alpha.3",
+ "clarity-angular": "^0.8.0",
+ "clarity-icons": "^0.8.0",
+ "clarity-ui": "^0.8.0",
+ "core-js": "^2.4.1",
+ "mutationobserver-shim": "^0.3.2",
+ "rxjs": "^5.0.1",
+ "ts-helpers": "^1.1.1",
+ "web-animations-js": "^2.2.1",
+ "zone.js": "^0.7.2"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "^2.4.1",
+ "@types/core-js": "^0.9.34",
+ "@types/jasmine": "^2.2.30",
+ "@types/node": "^6.0.42",
+ "angular-cli": "^1.0.0-beta.24",
+ "bootstrap": "4.0.0-alpha.5",
+ "codelyzer": "~1.0.0-beta.3",
+ "enhanced-resolve": "^3.0.0",
+ "jasmine-core": "2.4.1",
+ "jasmine-spec-reporter": "2.5.0",
+ "karma": "1.2.0",
+ "karma-cli": "^1.0.1",
+ "karma-jasmine": "^1.0.2",
+ "karma-mocha-reporter": "^2.2.1",
+ "karma-phantomjs-launcher": "^1.0.0",
+ "karma-remap-istanbul": "^0.2.1",
+ "protractor": "4.0.9",
+ "ts-node": "1.2.1",
+ "tslint": "^4.1.1",
+ "typescript": "~2.0.3",
+ "typings": "^1.4.0",
+ "webdriver-manager": "10.2.5"
+ }
+}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/account.module.ts b/harbor-app/src/app/account/account.module.ts
index f701c9753..9aa924a84 100644
--- a/harbor-app/src/app/account/account.module.ts
+++ b/harbor-app/src/app/account/account.module.ts
@@ -1,5 +1,5 @@
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 { RouterModule } from '@angular/router';
diff --git a/harbor-app/src/app/account/sign-in.component.html b/harbor-app/src/app/account/sign-in.component.html
deleted file mode 100644
index b51046ab0..000000000
--- a/harbor-app/src/app/account/sign-in.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in.component.ts b/harbor-app/src/app/account/sign-in.component.ts
deleted file mode 100644
index 0647801e7..000000000
--- a/harbor-app/src/app/account/sign-in.component.ts
+++ /dev/null
@@ -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){}
-}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in/sign-in-credential.ts b/harbor-app/src/app/account/sign-in/sign-in-credential.ts
new file mode 100644
index 000000000..eb639416e
--- /dev/null
+++ b/harbor-app/src/app/account/sign-in/sign-in-credential.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in/sign-in.component.css b/harbor-app/src/app/account/sign-in/sign-in.component.css
new file mode 100644
index 000000000..6004a33f6
--- /dev/null
+++ b/harbor-app/src/app/account/sign-in/sign-in.component.css
@@ -0,0 +1,7 @@
+.progress-size-small {
+ height: 0.5em !important;
+}
+
+.visibility-hidden {
+ visibility: hidden;
+}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in/sign-in.component.html b/harbor-app/src/app/account/sign-in/sign-in.component.html
new file mode 100644
index 000000000..d88f07c92
--- /dev/null
+++ b/harbor-app/src/app/account/sign-in/sign-in.component.html
@@ -0,0 +1,39 @@
+
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in/sign-in.component.ts b/harbor-app/src/app/account/sign-in/sign-in.component.ts
new file mode 100644
index 000000000..1b8a8617d
--- /dev/null
+++ b/harbor-app/src/app/account/sign-in/sign-in.component.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-in/sign-in.service.ts b/harbor-app/src/app/account/sign-in/sign-in.service.ts
new file mode 100644
index 000000000..c6a798451
--- /dev/null
+++ b/harbor-app/src/app/account/sign-in/sign-in.service.ts
@@ -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{
+ return Promise.reject(error.message || error);
+ }
+
+ //Submit signin form to backend (NOT restful service)
+ signIn(signInCredential: SignInCredential): Promise{
+ //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);
+ }
+}
\ No newline at end of file
diff --git a/harbor-app/src/app/account/sign-up.component.html b/harbor-app/src/app/account/sign-up.component.html
index e69de29bb..fae4c1f44 100644
--- a/harbor-app/src/app/account/sign-up.component.html
+++ b/harbor-app/src/app/account/sign-up.component.html
@@ -0,0 +1 @@
+Placeholder for signup
\ No newline at end of file
diff --git a/harbor-app/src/app/harbor-routing.module.ts b/harbor-app/src/app/harbor-routing.module.ts
index 4ed93c39e..8a6e95e96 100644
--- a/harbor-app/src/app/harbor-routing.module.ts
+++ b/harbor-app/src/app/harbor-routing.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
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 = [
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },