mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 08:38:03 +01:00
Fixed some issues and merged latest codes.
This commit is contained in:
parent
3c112f2a2c
commit
3a621faba3
@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
rm -rf dist/*
|
||||
ng build
|
||||
cp index.html dist/index.html
|
||||
|
||||
|
@ -29,6 +29,7 @@ func initRouters() {
|
||||
beego.SetStaticPath("static/resources", "static/resources")
|
||||
beego.SetStaticPath("static/vendors", "static/vendors")
|
||||
beego.SetStaticPath("/ng", "static/dist")
|
||||
beego.SetStaticPath("/ng/harbor", "static/dist")
|
||||
beego.SetStaticPath("/ng/harbor/dashboard", "static/dist")
|
||||
beego.SetStaticPath("/ng/harbor/projects", "static/dist")
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalSize]="'lg'">
|
||||
<h3 class="modal-title">Accout Settings</h3>
|
||||
<div class="modal-body">
|
||||
<form #accountSettingsFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="account_settings_username" class="col-md-4">Username</label>
|
||||
<input type="text" name="account_settings_username" [(ngModel)]="account.username" disabled id="account_settings_username" size="51">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_email" class="col-md-4 required">Email</label>
|
||||
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-right" [class.invalid]="eamilInput.invalid && (eamilInput.dirty || eamilInput.touched)">
|
||||
<input name="account_settings_email" type="text" #eamilInput="ngModel" [(ngModel)]="account.email"
|
||||
required
|
||||
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="account_settings_email" size="48">
|
||||
<span class="tooltip-content">
|
||||
Email should be a valid email address like name@example.com
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_full_name" class="col-md-4 required">Full name</label>
|
||||
<label for="account_settings_email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-right" [class.invalid]="fullNameInput.invalid && (fullNameInput.dirty || fullNameInput.touched)">
|
||||
<input type="text" name="account_settings_full_name" #fullNameInput="ngModel" [(ngModel)]="account.realname" required maxLength="20" maxLengthExt="20" id="account_settings_full_name" size="48">
|
||||
<span class="tooltip-content">
|
||||
Max length of full name is 20
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_comments" class="col-md-4">Comments</label>
|
||||
<input type="text" name="account_settings_comments" [(ngModel)]="account.comment" id="account_settings_comments" size="51">
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [hidden]='errorMessage === ""'>
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
This alert indicates success.
|
||||
</span>
|
||||
</div>
|
||||
</clr-alert>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || showProgress" (click)="submit()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,92 @@
|
||||
import { Component, OnInit, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
@Component({
|
||||
selector: "account-settings-modal",
|
||||
templateUrl: "account-settings-modal.component.html"
|
||||
})
|
||||
|
||||
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
opened: boolean = false;
|
||||
staticBackdrop: boolean = true;
|
||||
account: SessionUser;
|
||||
error: any;
|
||||
|
||||
private isOnCalling: boolean = false;
|
||||
private formValueChanged: boolean = false;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom") accountForm: NgForm;
|
||||
|
||||
constructor(private session: SessionService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//Value copy
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this.accountForm && this.accountForm.valid;
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.isOnCalling;
|
||||
}
|
||||
|
||||
public get errorMessage(): string {
|
||||
return this.error ? (this.error.message ? this.error.message : this.error) : "";
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.accountFormRef != this.accountForm) {
|
||||
this.accountFormRef = this.accountForm;
|
||||
if (this.accountFormRef) {
|
||||
this.accountFormRef.valueChanges.subscribe(data => {
|
||||
if (this.error) {
|
||||
this.error = null;
|
||||
}
|
||||
this.formValueChanged = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
this.formValueChanged = false;
|
||||
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (!this.isValid || this.isOnCalling) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Double confirm session is valid
|
||||
let cUser = this.session.getCurrentUser();
|
||||
if (!cUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isOnCalling = true;
|
||||
|
||||
this.session.updateAccountSettings(this.account)
|
||||
.then(() => {
|
||||
this.isOnCalling = false;
|
||||
this.close();
|
||||
})
|
||||
.catch(error => {
|
||||
this.isOnCalling = false;
|
||||
this.error = error
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CoreModule } from '../core/core.module';
|
||||
|
||||
import { SignInComponent } from './sign-in/sign-in.component';
|
||||
import { PasswordSettingComponent } from './password/password-setting.component';
|
||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||
|
||||
import { PasswordSettingService } from './password/password-setting.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
CoreModule,
|
||||
RouterModule
|
||||
],
|
||||
declarations: [SignInComponent],
|
||||
exports: [SignInComponent]
|
||||
declarations: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||
exports: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||
|
||||
providers: [PasswordSettingService]
|
||||
})
|
||||
export class AccountModule { }
|
@ -0,0 +1,55 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">Change Password</h3>
|
||||
<div class="modal-body" style="min-height: 250px; overflow-y: hidden;">
|
||||
<form #changepwdForm="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="oldPassword">Current Password</label>
|
||||
<label for="oldPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="oldPassInput.invalid && (oldPassInput.dirty || oldPassInput.touched)">
|
||||
<input type="password" id="oldPassword" placeholder="Enter current password"
|
||||
required
|
||||
name="oldPassword"
|
||||
[(ngModel)]="oldPwd"
|
||||
#oldPassInput="ngModel" size="25">
|
||||
<span class="tooltip-content">
|
||||
Current password is Required.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newPassword">New Password</label>
|
||||
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="newPassInput.invalid && (newPassInput.dirty || newPassInput.touched)">
|
||||
<input type="password" id="newPassword" placeholder="Enter new password"
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
||||
name="newPassword"
|
||||
[(ngModel)]="newPwd"
|
||||
#newPassInput="ngModel" size="25">
|
||||
<span class="tooltip-content">
|
||||
Password should be at least 7 characters with 1 uppercase, 1 lowercase letter and 1 number
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reNewPassword">Confirm Password</label>
|
||||
<label for="reNewPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]="(reNewPassInput.invalid && (reNewPassInput.dirty || reNewPassInput.touched)) || (!newPassInput.invalid && reNewPassInput.value != newPassInput.value)">
|
||||
<input type="password" id="reNewPassword" placeholder="Confirm new password"
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
||||
name="reNewPassword"
|
||||
[(ngModel)]="reNewPwd"
|
||||
#reNewPassInput="ngModel" size="25">
|
||||
<span class="tooltip-content">
|
||||
Password should be at least 7 characters with 1 uppercase, 1 lowercase letter and 1 number and same with new password
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline" style="top:8px;" [hidden]="showProgress === false"></span>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || showProgress" (click)="doOk()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
104
src/ui/static/app/account/password/password-setting.component.ts
Normal file
104
src/ui/static/app/account/password/password-setting.component.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Component, ViewChild, AfterViewChecked, Output, EventEmitter } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { PasswordSettingService } from './password-setting.service';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
@Component({
|
||||
selector: 'password-setting',
|
||||
templateUrl: "password-setting.component.html"
|
||||
})
|
||||
export class PasswordSettingComponent implements AfterViewChecked {
|
||||
opened: boolean = false;
|
||||
oldPwd: string = "";
|
||||
newPwd: string = "";
|
||||
reNewPwd: string = "";
|
||||
|
||||
private formValueChanged: boolean = false;
|
||||
private onCalling: boolean = false;
|
||||
|
||||
pwdFormRef: NgForm;
|
||||
@ViewChild("changepwdForm") pwdForm: NgForm;
|
||||
|
||||
@Output() private pwdChange = new EventEmitter<any>();
|
||||
|
||||
constructor(private passwordService: PasswordSettingService, private session: SessionService){}
|
||||
|
||||
//If form is valid
|
||||
public get isValid(): boolean {
|
||||
if (this.pwdForm && this.pwdForm.form.get("newPassword")) {
|
||||
return this.pwdForm.valid &&
|
||||
this.pwdForm.form.get("newPassword").value === this.pwdForm.form.get("reNewPassword").value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public get valueChanged(): boolean {
|
||||
return this.formValueChanged;
|
||||
}
|
||||
|
||||
public get showProgress(): boolean {
|
||||
return this.onCalling;
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
if (this.pwdFormRef != this.pwdForm) {
|
||||
this.pwdFormRef = this.pwdForm;
|
||||
if (this.pwdFormRef) {
|
||||
this.pwdFormRef.valueChanges.subscribe(data => {
|
||||
this.formValueChanged = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Open modal dialog
|
||||
open(): void {
|
||||
this.opened = true;
|
||||
this.pwdForm.reset();
|
||||
}
|
||||
|
||||
//Close the moal dialog
|
||||
close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
//handle the ok action
|
||||
doOk(): void {
|
||||
if (this.onCalling) {
|
||||
return;//To avoid duplicate click events
|
||||
}
|
||||
|
||||
if (!this.isValid) {
|
||||
return;//Double confirm
|
||||
}
|
||||
|
||||
//Double confirm session is valid
|
||||
let cUser = this.session.getCurrentUser();
|
||||
if(!cUser){
|
||||
return;
|
||||
}
|
||||
|
||||
//Call service
|
||||
this.onCalling = true;
|
||||
|
||||
this.passwordService.changePassword(cUser.user_id,
|
||||
{
|
||||
new_password: this.pwdForm.value.newPassword,
|
||||
old_password: this.pwdForm.value.oldPassword
|
||||
})
|
||||
.then(() => {
|
||||
this.onCalling = false;
|
||||
//Tell shell to reset current view
|
||||
this.pwdChange.emit(true);
|
||||
|
||||
this.close();
|
||||
})
|
||||
.catch(error => {
|
||||
this.onCalling = false;
|
||||
console.error(error);//TODO:
|
||||
});
|
||||
//TODO:publish the successful message to general messae box
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { PasswordSetting } from './password-setting';
|
||||
|
||||
const passwordChangeEndpoint = "/api/users/:user_id/password";
|
||||
|
||||
@Injectable()
|
||||
export class PasswordSettingService {
|
||||
private headers: Headers = new Headers({
|
||||
"Accept": 'application/json',
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
private options: RequestOptions = new RequestOptions({
|
||||
'headers': this.headers
|
||||
});
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
changePassword(userId: number, setting: PasswordSetting): Promise<any> {
|
||||
if(!setting || setting.new_password.trim()==="" || setting.old_password.trim()===""){
|
||||
return Promise.reject("Invalid data");
|
||||
}
|
||||
|
||||
let putUrl = passwordChangeEndpoint.replace(":user_id", userId+"");
|
||||
return this.http.put(putUrl, JSON.stringify(setting), this.options)
|
||||
.toPromise()
|
||||
.then(() => null)
|
||||
.catch(error=>{
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
11
src/ui/static/app/account/password/password-setting.ts
Normal file
11
src/ui/static/app/account/password/password-setting.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
*
|
||||
* Struct for password change
|
||||
*
|
||||
* @export
|
||||
* @class PasswordSetting
|
||||
*/
|
||||
export class PasswordSetting {
|
||||
old_password: string;
|
||||
new_password: string;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'reset-password',
|
||||
templateUrl: "reset-password.component.html"
|
||||
})
|
||||
export class ResetPasswordComponent {
|
||||
// constructor(private router: Router){}
|
||||
}
|
@ -3,9 +3,8 @@ 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'
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { SignInCredential } from '../../shared/sign-in-credential';
|
||||
|
||||
//Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
@ -15,9 +14,7 @@ export const signInStatusError = -1;
|
||||
@Component({
|
||||
selector: 'sign-in',
|
||||
templateUrl: "sign-in.component.html",
|
||||
styleUrls: ['sign-in.component.css'],
|
||||
|
||||
providers: [SignInService]
|
||||
styleUrls: ['sign-in.component.css']
|
||||
})
|
||||
|
||||
export class SignInComponent implements AfterViewChecked {
|
||||
@ -35,7 +32,6 @@ export class SignInComponent implements AfterViewChecked {
|
||||
};
|
||||
|
||||
constructor(
|
||||
private signInService: SignInService,
|
||||
private router: Router,
|
||||
private session: SessionService
|
||||
) { }
|
||||
@ -97,8 +93,7 @@ export class SignInComponent implements AfterViewChecked {
|
||||
//Trigger the signin action
|
||||
signIn(): void {
|
||||
//Should validate input firstly
|
||||
if (!this.validate()) {
|
||||
console.info("return");
|
||||
if (!this.validate() || this.signInStatus === signInStatusOnGoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,21 +101,25 @@ export class SignInComponent implements AfterViewChecked {
|
||||
this.signInStatus = signInStatusOnGoing;
|
||||
|
||||
//Call the service to send out the http request
|
||||
this.signInService.signIn(this.signInCredential)
|
||||
this.session.signIn(this.signInCredential)
|
||||
.then(() => {
|
||||
//Set status
|
||||
this.signInStatus = signInStatusNormal;
|
||||
|
||||
//Validate the sign-in session
|
||||
this.session.retrieveUser()
|
||||
.then(() => {
|
||||
.then(user => {
|
||||
//Routing to the right location
|
||||
let nextRoute = ["/harbor", "dashboard"];
|
||||
let nextRoute = ["/harbor", "projects"];
|
||||
this.router.navigate(nextRoute);
|
||||
})
|
||||
.catch(this.handleError);
|
||||
.catch(error => {
|
||||
this.handleError(error);
|
||||
});
|
||||
})
|
||||
.catch(this.handleError);
|
||||
.catch(error => {
|
||||
this.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
//Help user navigate to the sign up
|
||||
|
@ -4,7 +4,7 @@ import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
|
||||
const url_prefix = '';
|
||||
const url_prefix = '/ng';
|
||||
const signInUrl = url_prefix + '/login';
|
||||
/**
|
||||
*
|
||||
|
@ -4,25 +4,23 @@ import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { ClarityModule } from 'clarity-angular';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AccountModule } from './account/account.module';
|
||||
|
||||
import { BaseModule } from './base/base.module';
|
||||
|
||||
import { HarborRoutingModule } from './harbor-routing.module';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreModule,
|
||||
AccountModule,
|
||||
SharedModule,
|
||||
BaseModule,
|
||||
HarborRoutingModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [ AppComponent ]
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
|
23
src/ui/static/app/base/base-routing-resolver.service.ts
Normal file
23
src/ui/static/app/base/base-routing-resolver.service.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
Router, Resolve, ActivatedRouteSnapshot, RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { SessionUser } from '../shared/session-user';
|
||||
|
||||
@Injectable()
|
||||
export class BaseRoutingResolver implements Resolve<SessionUser> {
|
||||
|
||||
constructor(private session: SessionService, private router: Router) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SessionUser> {
|
||||
return this.session.retrieveUser()
|
||||
.then(sessionUser => {
|
||||
return sessionUser;
|
||||
})
|
||||
.catch(error => {
|
||||
console.info("Anonymous user");
|
||||
});
|
||||
}
|
||||
}
|
@ -5,12 +5,21 @@ import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
import { DashboardComponent } from '../dashboard/dashboard.component';
|
||||
import { ProjectComponent } from '../project/project.component';
|
||||
|
||||
import { BaseRoutingResolver } from './base-routing-resolver.service';
|
||||
|
||||
const baseRoutes: Routes = [
|
||||
{
|
||||
path: 'harbor', component: HarborShellComponent,
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
children: [
|
||||
{ path: 'dashboard', component: DashboardComponent },
|
||||
{ path: 'projects', component: ProjectComponent }
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
@ -18,7 +27,9 @@ const baseRoutes: Routes = [
|
||||
imports: [
|
||||
RouterModule.forChild(baseRoutes)
|
||||
],
|
||||
exports: [ RouterModule ]
|
||||
exports: [RouterModule],
|
||||
|
||||
providers: [BaseRoutingResolver]
|
||||
})
|
||||
export class BaseRoutingModule {
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-right'">
|
||||
<button class="nav-text" clrDropdownToggle>
|
||||
<!--<clr-icon shape="user" class="is-inverse" size="24"></clr-icon>-->
|
||||
<span>Administrator</span>
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Add User</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Account Setting</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>About</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
@ -1,16 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: "base-settings",
|
||||
templateUrl: "base-settings.component.html"
|
||||
})
|
||||
|
||||
/**
|
||||
* Component to handle the account settings
|
||||
*/
|
||||
export class BaseSettingsComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,7 @@ import { NavigatorComponent } from './navigator/navigator.component';
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
import { BaseSettingsComponent } from './base-settings/base-settings.component';
|
||||
import { SearchResultComponent } from './global-search/search-result.component';
|
||||
|
||||
import { BaseRoutingModule } from './base-routing.module';
|
||||
|
||||
@ -24,9 +24,9 @@ import { BaseRoutingModule } from './base-routing.module';
|
||||
declarations: [
|
||||
NavigatorComponent,
|
||||
GlobalSearchComponent,
|
||||
BaseSettingsComponent,
|
||||
FooterComponent,
|
||||
HarborShellComponent
|
||||
HarborShellComponent,
|
||||
SearchResultComponent
|
||||
],
|
||||
exports: [ HarborShellComponent ]
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
<a class="nav-link">
|
||||
<span class="nav-text">
|
||||
<clr-icon shape="search"></clr-icon>
|
||||
</span>
|
||||
</a>
|
||||
<form class="search">
|
||||
<label for="search_input">
|
||||
<input #globalSearchBox id="search_input" type="text" (keyup)="search(globalSearchBox.value)" placeholder="Search Harbor...">
|
||||
</label>
|
||||
</form>
|
@ -1,10 +1,44 @@
|
||||
import { Component} from '@angular/core';
|
||||
import { Component, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SearchEvent } from '../search-event';
|
||||
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
const deBounceTime = 500; //ms
|
||||
|
||||
@Component({
|
||||
selector: 'global-search',
|
||||
templateUrl: "global-search.component.html"
|
||||
})
|
||||
export class GlobalSearchComponent{
|
||||
// constructor(private router: Router){}
|
||||
export class GlobalSearchComponent implements OnInit {
|
||||
//Publish search event to parent
|
||||
@Output() searchEvt = new EventEmitter<SearchEvent>();
|
||||
|
||||
//Keep search term as Subject
|
||||
private searchTerms = new Subject<string>();
|
||||
|
||||
//Implement ngOnIni
|
||||
ngOnInit(): void {
|
||||
this.searchTerms
|
||||
.debounceTime(deBounceTime)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(term => {
|
||||
this.searchEvt.emit({
|
||||
term: term
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Handle the term inputting event
|
||||
search(term: string): void {
|
||||
//Send event only when term is not empty
|
||||
|
||||
let nextTerm = term.trim();
|
||||
if (nextTerm != "") {
|
||||
this.searchTerms.next(nextTerm);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SearchResults } from './search-results';
|
||||
|
||||
const searchEndpoint = "/api/search";
|
||||
/**
|
||||
* Declare service to handle the global search
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @class GlobalSearchService
|
||||
*/
|
||||
@Injectable()
|
||||
export class GlobalSearchService {
|
||||
private headers = new Headers({
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
private options = new RequestOptions({
|
||||
headers: this.headers
|
||||
});
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
/**
|
||||
* Search related artifacts with the provided keyword
|
||||
*
|
||||
* @param {string} keyword
|
||||
* @returns {Promise<SearchResults>}
|
||||
*
|
||||
* @memberOf GlobalSearchService
|
||||
*/
|
||||
doSearch(term: string): Promise<SearchResults> {
|
||||
let searchUrl = searchEndpoint + "?q=" + term;
|
||||
|
||||
return this.http.get(searchUrl, this.options).toPromise()
|
||||
.then(response => response.json() as SearchResults)
|
||||
.catch(error => error);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
.search-overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 94%;
|
||||
width: 97%;
|
||||
/*shoud be lesser than 1000 to aoivd override the popup menu*/
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
margin-top: 0px;
|
||||
font-size: 28px;
|
||||
letter-spacing: normal;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.search-close {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-parent-override {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.search-spinner {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<div class="search-overlay" *ngIf="state">
|
||||
<div class="search-header">
|
||||
<span class="search-title">Search results</span>
|
||||
<span class="search-close" (mouseover)="mouseAction(true)" (mouseout)="mouseAction(false)">
|
||||
<clr-icon shape="close" [class.is-highlight]="hover" size="36" (click)="close()"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
<!-- spinner -->
|
||||
<div class="spinner spinner-lg search-spinner" [hidden]="done">Search...</div>
|
||||
<div>Results is showing here!</div>
|
||||
</div>
|
@ -0,0 +1,80 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
import { GlobalSearchService } from './global-search.service';
|
||||
import { SearchResults } from './search-results';
|
||||
|
||||
@Component({
|
||||
selector: "search-result",
|
||||
templateUrl: "search-result.component.html",
|
||||
styleUrls: ["search-result.component.css"],
|
||||
|
||||
providers: [GlobalSearchService]
|
||||
})
|
||||
|
||||
export class SearchResultComponent {
|
||||
@Output() closeEvt = new EventEmitter<boolean>();
|
||||
|
||||
searchResults: SearchResults;
|
||||
|
||||
//Open or close
|
||||
private stateIndicator: boolean = false;
|
||||
//Search in progress
|
||||
private onGoing: boolean = true;
|
||||
|
||||
//Whether or not mouse point is onto the close indicator
|
||||
private mouseOn: boolean = false;
|
||||
|
||||
constructor(private search: GlobalSearchService) { }
|
||||
|
||||
public get state(): boolean {
|
||||
return this.stateIndicator;
|
||||
}
|
||||
|
||||
public get done(): boolean {
|
||||
return !this.onGoing;
|
||||
}
|
||||
|
||||
public get hover(): boolean {
|
||||
return this.mouseOn;
|
||||
}
|
||||
|
||||
//Handle mouse event of close indicator
|
||||
mouseAction(over: boolean): void {
|
||||
this.mouseOn = over;
|
||||
}
|
||||
|
||||
//Show the results
|
||||
show(): void {
|
||||
this.stateIndicator = true;
|
||||
}
|
||||
|
||||
//Close the result page
|
||||
close(): void {
|
||||
//Tell shell close
|
||||
this.closeEvt.emit(true);
|
||||
|
||||
this.stateIndicator = false;
|
||||
}
|
||||
|
||||
//Call search service to complete the search request
|
||||
doSearch(term: string): void {
|
||||
//Confirm page is displayed
|
||||
if (!this.stateIndicator) {
|
||||
this.show();
|
||||
}
|
||||
|
||||
//Show spinner
|
||||
this.onGoing = true;
|
||||
|
||||
this.search.doSearch(term)
|
||||
.then(searchResults => {
|
||||
this.onGoing = false;
|
||||
this.searchResults = searchResults;
|
||||
console.info(searchResults);
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
console.error(error);//TODO: Use general erro handler
|
||||
});
|
||||
}
|
||||
}
|
7
src/ui/static/app/base/global-search/search-results.ts
Normal file
7
src/ui/static/app/base/global-search/search-results.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Project } from '../../project/project';
|
||||
import { Repository } from '../../repository/repository';
|
||||
|
||||
export class SearchResults {
|
||||
projects: Project[];
|
||||
repositories: Repository[];
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.side-nav-override {
|
||||
box-shadow: 6px 0px 0px 0px #ccc;
|
||||
}
|
||||
|
||||
.container-override {
|
||||
position: relative !important;
|
||||
}
|
@ -1,41 +1,30 @@
|
||||
<clr-main-container>
|
||||
<clr-modal [(clrModalOpen)]="account_settings_opened">
|
||||
<h3 class="modal-title">Accout Settings</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="account_settings_username" class="col-md-4">Username</label>
|
||||
<input type="text" class="col-md-8" id="account_settings_username" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_email" class="col-md-4">Email</label>
|
||||
<input type="text" class="col-md-8" id="account_settings_email" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_full_name" class="col-md-4">Full name</label>
|
||||
<input type="text" class="col-md-8" id="account_settings_full_name" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_settings_comments" class="col-md-4">Comments</label>
|
||||
<input type="text" class="col-md-8" id="account_settings_comments" size="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="col-md-4" class="btn btn-outline">Change Password</button>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<navigator (showAccountSettingsModal)="openModal($event)" (searchEvt)="doSearch($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
||||
<global-message></global-message>
|
||||
<div class="content-container">
|
||||
<div class="content-area" [class.container-override]="showSearch">
|
||||
<!-- Only appear when searching -->
|
||||
<search-result (closeEvt)="searchClose($event)"></search-result>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<nav class="sidenav" [class.side-nav-override]="showSearch">
|
||||
<section class="sidenav-content">
|
||||
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">
|
||||
Projects
|
||||
</a>
|
||||
<section class="nav-group collapsible">
|
||||
<input id="tabsystem" type="checkbox">
|
||||
<label for="tabsystem">System Managements</label>
|
||||
<ul class="nav-list">
|
||||
<li><a class="nav-link">Users</a></li>
|
||||
<li><a class="nav-link">Replications</a></li>
|
||||
<li><a class="nav-link">Quarantine[*]</a></li>
|
||||
<li><a class="nav-link">Configurations[*]</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="account_settings_opened = false">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="account_settings_opened = false">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<navigator></navigator>
|
||||
<global-message></global-message>
|
||||
<div class="content-container">
|
||||
<div class="content-area">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</clr-main-container>
|
||||
<account-settings-modal></account-settings-modal>
|
||||
<password-setting (pwdChange)="watchPwdChange($event)"></password-setting>
|
@ -1,10 +1,87 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { SearchEvent } from '../search-event';
|
||||
import { modalAccountSettings, modalPasswordSetting } from '../modal-events.const';
|
||||
|
||||
import { AccountSettingsModalComponent } from '../../account/account-settings/account-settings-modal.component';
|
||||
import { SearchResultComponent } from '../global-search/search-result.component';
|
||||
import { PasswordSettingComponent } from '../../account/password/password-setting.component';
|
||||
import { NavigatorComponent } from '../navigator/navigator.component';
|
||||
|
||||
@Component({
|
||||
selector: 'harbor-shell',
|
||||
templateUrl: 'harbor-shell.component.html'
|
||||
templateUrl: 'harbor-shell.component.html',
|
||||
styleUrls: ["harbor-shell.component.css"]
|
||||
})
|
||||
export class HarborShellComponent {
|
||||
|
||||
|
||||
export class HarborShellComponent implements OnInit {
|
||||
|
||||
@ViewChild(AccountSettingsModalComponent)
|
||||
private accountSettingsModal: AccountSettingsModalComponent;
|
||||
|
||||
@ViewChild(SearchResultComponent)
|
||||
private searchResultComponet: SearchResultComponent;
|
||||
|
||||
@ViewChild(PasswordSettingComponent)
|
||||
private pwdSetting: PasswordSettingComponent;
|
||||
|
||||
@ViewChild(NavigatorComponent)
|
||||
private navigator: NavigatorComponent;
|
||||
|
||||
//To indicator whwther or not the search results page is displayed
|
||||
//We need to use this property to do some overriding work
|
||||
private isSearchResultsOpened: boolean = false;
|
||||
|
||||
constructor(private route: ActivatedRoute) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe(data => {
|
||||
//dummy
|
||||
});
|
||||
}
|
||||
|
||||
public get showSearch(): boolean {
|
||||
return this.isSearchResultsOpened;
|
||||
}
|
||||
|
||||
//Open modal dialog
|
||||
openModal(event: ModalEvent): void {
|
||||
switch (event.modalName) {
|
||||
case modalAccountSettings:
|
||||
this.accountSettingsModal.open();
|
||||
break;
|
||||
case modalPasswordSetting:
|
||||
this.pwdSetting.open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Handle the global search event and then let the result page to trigger api
|
||||
doSearch(event: SearchEvent): void {
|
||||
//Once this method is called
|
||||
//the search results page must be opened
|
||||
this.isSearchResultsOpened = true;
|
||||
|
||||
//Call the child component to do the real work
|
||||
this.searchResultComponet.doSearch(event.term);
|
||||
}
|
||||
|
||||
//Search results page closed
|
||||
//remove the related ovevriding things
|
||||
searchClose(event: boolean): void {
|
||||
if (event) {
|
||||
this.isSearchResultsOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Watch password whether changed
|
||||
watchPwdChange(event: any): void {
|
||||
if (event) {
|
||||
this.navigator.logOut(true);
|
||||
}
|
||||
}
|
||||
}
|
5
src/ui/static/app/base/modal-event.ts
Normal file
5
src/ui/static/app/base/modal-event.ts
Normal file
@ -0,0 +1,5 @@
|
||||
//Define a object to store the modal event
|
||||
export class ModalEvent {
|
||||
modalName: string;
|
||||
modalFlag: boolean; //true for open, false for close
|
||||
}
|
2
src/ui/static/app/base/modal-events.const.ts
Normal file
2
src/ui/static/app/base/modal-events.const.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const modalAccountSettings= "account-settings";
|
||||
export const modalPasswordSetting = "password-setting";
|
16
src/ui/static/app/base/navigator/navigator.component.css
Normal file
16
src/ui/static/app/base/navigator/navigator.component.css
Normal file
@ -0,0 +1,16 @@
|
||||
.sign-in-override {
|
||||
padding-left: 0px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
.sign-up-override {
|
||||
padding-left: 5px !important;
|
||||
}
|
||||
|
||||
.custom-divider {
|
||||
display: inline-block;
|
||||
border-right: 2px inset snow;
|
||||
padding: 2px 0px 2px 0px;
|
||||
vertical-align: middle;
|
||||
height: 24px;
|
||||
}
|
@ -1,40 +1,51 @@
|
||||
<clr-header class="header-4 header">
|
||||
<clr-header class="header-5 header">
|
||||
<div class="branding">
|
||||
<a href="#" class="nav-link">
|
||||
<clr-icon shape="vm-bug"></clr-icon>
|
||||
<span class="title">Harbor</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="header-nav">
|
||||
<a class="nav-link" id="dashboard-link" [routerLink]="['/harbor', 'dashboard']" routerLinkActive="active">
|
||||
<span class="nav-text">Dashboard</span>
|
||||
</a>
|
||||
<a class="nav-link" id="dashboard-link" [routerLink]="['/harbor', 'projects']" routerLinkActive="active">
|
||||
<span class="nav-text">Project</span>
|
||||
</a>
|
||||
</div>
|
||||
<global-search (searchEvt)="transferSearchEvent($event)"></global-search>
|
||||
<div class="header-actions">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-right'">
|
||||
<!--<clr-icon shape="user" class="is-inverse" size="24"></clr-icon>-->
|
||||
<div *ngIf="!isSessionValid">
|
||||
<a href="javascript:void(0)" class="nav-link nav-text sign-in-override" routerLink="/sign-in" routerLinkActive="active">Sign In</a>
|
||||
<span class="custom-divider"></span>
|
||||
<a href="javascript:void(0)" class="nav-link nav-text sign-up-override" routerLink="/sign-up" routerLinkActive="active">Sign Up</a>
|
||||
</div>
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'" class="dropdown" *ngIf="isSessionValid">
|
||||
<button class="nav-text" clrDropdownToggle>
|
||||
<clr-icon shape="user" class="is-inverse" size="24" style="left: -2px;"></clr-icon>
|
||||
<span>Administrator</span>
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Add User</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Account Setting</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>About</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">Account Settings</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="openChangePwdModal()">Change Password</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="logOut(false)">Log out</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<clr-dropdown class="dropdown bottom-left">
|
||||
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
||||
<clr-icon shape="world" style="left:-5px;"></clr-icon>
|
||||
<span>English</span>
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>English</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>中文简体</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>中文繁體</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<global-search></global-search>
|
||||
<clr-dropdown class="dropdown bottom-right">
|
||||
<button class="nav-text" clrDropdownToggle>
|
||||
<button class="nav-icon" clrDropdownToggle>
|
||||
<clr-icon shape="cog"></clr-icon>
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Preferences</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Log out</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Add User</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>About</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
|
@ -1,10 +1,71 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { SearchEvent } from '../search-event';
|
||||
import { modalAccountSettings, modalPasswordSetting } from '../modal-events.const';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
@Component({
|
||||
selector: 'navigator',
|
||||
templateUrl: "navigator.component.html"
|
||||
templateUrl: "navigator.component.html",
|
||||
styleUrls: ["navigator.component.css"]
|
||||
})
|
||||
export class NavigatorComponent {
|
||||
|
||||
export class NavigatorComponent implements OnInit {
|
||||
// constructor(private router: Router){}
|
||||
@Output() showAccountSettingsModal = new EventEmitter<ModalEvent>();
|
||||
@Output() searchEvt = new EventEmitter<SearchEvent>();
|
||||
@Output() showPwdChangeModal = new EventEmitter<ModalEvent>();
|
||||
|
||||
private sessionUser: SessionUser = null;
|
||||
|
||||
constructor(private session: SessionService, private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sessionUser = this.session.getCurrentUser();
|
||||
}
|
||||
|
||||
public get isSessionValid(): boolean {
|
||||
return this.sessionUser != null;
|
||||
}
|
||||
|
||||
//Open the account setting dialog
|
||||
openAccountSettingsModal(): void {
|
||||
this.showAccountSettingsModal.emit({
|
||||
modalName: modalAccountSettings,
|
||||
modalFlag: true
|
||||
});
|
||||
}
|
||||
|
||||
//Open change password dialog
|
||||
openChangePwdModal(): void {
|
||||
this.showPwdChangeModal.emit({
|
||||
modalName: modalPasswordSetting,
|
||||
modalFlag: true
|
||||
});
|
||||
}
|
||||
|
||||
//Only transfer the search event to the parent shell
|
||||
transferSearchEvent(evt: SearchEvent): void {
|
||||
this.searchEvt.emit(evt);
|
||||
}
|
||||
|
||||
//Log out system
|
||||
logOut(reSignIn: boolean): void {
|
||||
this.session.signOff()
|
||||
.then(() => {
|
||||
this.sessionUser = null;
|
||||
if (reSignIn) {
|
||||
//Naviagte to the sign in route
|
||||
this.router.navigate(["/sign-in"]);
|
||||
} else {
|
||||
//Naviagte to the default route
|
||||
this.router.navigate(["/harbor"]);
|
||||
}
|
||||
})
|
||||
.catch()//TODO:
|
||||
}
|
||||
}
|
4
src/ui/static/app/base/search-event.ts
Normal file
4
src/ui/static/app/base/search-event.ts
Normal file
@ -0,0 +1,4 @@
|
||||
//Define a object to store the search event
|
||||
export class SearchEvent {
|
||||
term: string;
|
||||
}
|
@ -3,9 +3,16 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
|
||||
|
||||
import { BaseRoutingResolver } from './base/base-routing-resolver.service';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent
|
||||
},
|
||||
{ path: '', redirectTo: '/harbor', pathMatch: 'full' },
|
||||
{ path: 'sign-in', component: SignInComponent }
|
||||
];
|
||||
|
||||
@ -13,7 +20,7 @@ const harborRoutes: Routes = [
|
||||
imports: [
|
||||
RouterModule.forRoot(harborRoutes)
|
||||
],
|
||||
exports: [ RouterModule ]
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HarborRoutingModule {
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<button class="action-item" (click)="onEdit(p)">Edit</button>
|
||||
<button class="action-item" (click)="onDelete(p)">Delete</button>
|
||||
</clr-dg-action-overflow>-->
|
||||
<clr-dg-cell><a [routerLink]="['/harbor', 'projects', p.id, 'repository']" >{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell><a [routerLink]="['/harbor', 'projects', p.project_id, 'repository']" >{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{p.public == 1 ? 'Public': 'Private'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
|
||||
|
@ -0,0 +1,37 @@
|
||||
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
||||
<h3 class="modal-title">Add Member</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="member_name" class="col-md-4">Username</label>
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="hasError" [class.valid]="!hasError" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" (keyup)="hasError=false;">
|
||||
<span class="tooltip-content">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4">Role</label>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" (click)="member.role_id = 1" [checked]="member.role_id === 1">
|
||||
<label for="checkrads_project_admin">Project Admin</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" (click)="member.role_id = 2" [checked]="member.role_id === 2">
|
||||
<label for="checkrads_developer">Developer</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" (click)="member.role_id = 3" [checked]="member.role_id === 3">
|
||||
<label for="checkrads_guest">Guest</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="addMemberOpened = false">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="onSubmit()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,61 @@
|
||||
import { Component, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
import { MemberService } from '../member.service';
|
||||
import { MessageService } from '../../../global-message/message.service';
|
||||
import { Member } from '../member';
|
||||
|
||||
@Component({
|
||||
selector: 'add-member',
|
||||
templateUrl: 'add-member.component.html'
|
||||
})
|
||||
export class AddMemberComponent {
|
||||
|
||||
member: Member = new Member();
|
||||
addMemberOpened: boolean;
|
||||
errorMessage: string;
|
||||
hasError: boolean;
|
||||
|
||||
@Input() projectId: number;
|
||||
@Output() added = new EventEmitter<boolean>();
|
||||
|
||||
constructor(private memberService: MemberService, private messageService: MessageService) {}
|
||||
|
||||
onSubmit(): void {
|
||||
this.hasError = false;
|
||||
console.log('Adding member:' + JSON.stringify(this.member));
|
||||
this.memberService
|
||||
.addMember(this.projectId, this.member.username, this.member.role_id)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Added member successfully.');
|
||||
this.added.emit(true);
|
||||
this.addMemberOpened = false;
|
||||
},
|
||||
error=>{
|
||||
this.hasError = true;
|
||||
if (error instanceof Response) {
|
||||
switch(error.status){
|
||||
case 404:
|
||||
this.errorMessage = 'Username does not exist.';
|
||||
break;
|
||||
case 409:
|
||||
this.errorMessage = 'Username already exists.';
|
||||
break;
|
||||
default:
|
||||
this.errorMessage = 'Unknow error occurred while adding member.';
|
||||
this.messageService.announceMessage(this.errorMessage);
|
||||
}
|
||||
}
|
||||
console.log('Failed to add member of project:' + this.projectId, ' with error:' + error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.hasError = false;
|
||||
this.member = new Member();
|
||||
this.addMemberOpened = true;
|
||||
}
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4">
|
||||
<button class="btn btn-sm">new user</button>
|
||||
<button class="btn btn-sm" (click)="openAddMemberModal()">new user</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<input type="text" placeholder="Search for users">
|
||||
<input type="text" placeholder="Search for users" #searchMember (keyup.enter)="doSearch(searchMember.value)">
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
@ -13,23 +14,25 @@
|
||||
<clr-dg-column>Role</clr-dg-column>
|
||||
<clr-dg-column>Action</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-cell>{{u.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{u.role}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{u.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[u.role_id]}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'" [hidden]="u.user_id === currentUser.user_id">
|
||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||
Actions
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Project Admin</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Developer</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Guest</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 1)">Project Admin</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 2)">Developer</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 3)">Guest</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="deleteMember(u.user_id)">Delete Member</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{members.length}} item(s)</clr-dg-footer>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} item(s)</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
@ -1,18 +1,90 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { Member } from './member';
|
||||
import { MemberService } from './member.service';
|
||||
|
||||
import { AddMemberComponent } from './add-member/add-member.component';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
export const roleInfo: {} = { 1: 'ProjectAdmin', 2: 'Developer', 3: 'Guest'};
|
||||
|
||||
@Component({
|
||||
templateUrl: 'member.component.html'
|
||||
})
|
||||
export class MemberComponent implements OnInit {
|
||||
|
||||
currentUser: SessionUser;
|
||||
members: Member[];
|
||||
projectId: number;
|
||||
roleInfo = roleInfo;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.members = [
|
||||
{ name: 'Admin', role: 'Sys admin'},
|
||||
{ name: 'user01', role: 'Project Admin'},
|
||||
{ name: 'user02', role: 'Developer'},
|
||||
{ name: 'user03', role: 'Guest'}
|
||||
];
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
constructor(private route: ActivatedRoute, private memberService: MemberService, private messageService: MessageService) {
|
||||
//Get current user from registered resolver.
|
||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
||||
}
|
||||
|
||||
retrieve(projectId:number, username: string) {
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
response=>this.members = response,
|
||||
error=>console.log(error)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
openAddMemberModal() {
|
||||
this.addMemberComponent.openAddMemberModal();
|
||||
}
|
||||
|
||||
addedMember() {
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
changeRole(userId: number, roleId: number) {
|
||||
this.memberService
|
||||
.changeMemberRole(this.projectId, userId, roleId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage('Failed to change role with user ' + userId + ' to roleId ' + roleId)
|
||||
);
|
||||
}
|
||||
|
||||
deleteMember(userId: number) {
|
||||
this.memberService
|
||||
.deleteMember(this.projectId, userId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful change role with user ' + userId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage('Failed to change role with user ' + userId)
|
||||
);
|
||||
}
|
||||
|
||||
doSearch(searchMember) {
|
||||
this.retrieve(this.projectId, searchMember);
|
||||
}
|
||||
}
|
52
src/ui/static/app/project/member/member.service.ts
Normal file
52
src/ui/static/app/project/member/member.service.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
import { BaseService } from '../../service/base.service';
|
||||
import { Member } from './member';
|
||||
|
||||
export const urlPrefix = '';
|
||||
|
||||
@Injectable()
|
||||
export class MemberService extends BaseService {
|
||||
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
|
||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
||||
return this.http
|
||||
.get(urlPrefix + `/api/projects/${projectId}/members?username=${username}`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>this.handleError(error));
|
||||
}
|
||||
|
||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||
console.log('Adding member with username:' + username + ', roleId:' + roleId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.post(urlPrefix + `/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] })
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
changeMemberRole(projectId: number, userId: number, roleId: number): Observable<any> {
|
||||
console.log('Changing member role with userId:' + ' to roleId:' + roleId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.put(urlPrefix + `/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deleteMember(projectId: number, userId: number): Observable<any> {
|
||||
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.delete(urlPrefix + `/api/projects/${projectId}/members/${userId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
}
|
@ -1,4 +1,25 @@
|
||||
/*
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"email": "",
|
||||
"password": "",
|
||||
"realname": "",
|
||||
"comment": "",
|
||||
"deleted": 0,
|
||||
"role_name": "projectAdmin",
|
||||
"role_id": 1,
|
||||
"has_admin_role": 0,
|
||||
"reset_uuid": "",
|
||||
"creation_time": "0001-01-01T00:00:00Z",
|
||||
"update_time": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
*/
|
||||
|
||||
export class Member {
|
||||
name: string;
|
||||
role: string;
|
||||
user_id: number;
|
||||
username: string;
|
||||
role_name: string;
|
||||
has_admin_role: number;
|
||||
role_id: number;
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'project-detail',
|
||||
templateUrl: "project-detail.component.html",
|
||||
styleUrls: [ 'project-detail.css' ]
|
||||
})
|
||||
export class ProjectDetailComponent {
|
||||
// constructor(private router: Router){}
|
||||
}
|
||||
export class ProjectDetailComponent {}
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { HarborShellComponent} from '../base/harbor-shell/harbor-shell.component';
|
||||
import { HarborShellComponent } from '../base/harbor-shell/harbor-shell.component';
|
||||
import { ProjectComponent } from './project.component';
|
||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||
|
||||
@ -10,18 +10,38 @@ import { ReplicationComponent } from '../replication/replication.component';
|
||||
import { MemberComponent } from './member/member.component';
|
||||
import { AuditLogComponent } from '../log/audit-log.component';
|
||||
|
||||
import { BaseRoutingResolver } from '../base/base-routing-resolver.service';
|
||||
|
||||
const projectRoutes: Routes = [
|
||||
{ path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
resolve: {
|
||||
harborResolver: BaseRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{ path: 'projects', component: ProjectComponent },
|
||||
{
|
||||
path: 'projects/:id',
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectComponent,
|
||||
resolve: {
|
||||
projectsResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
resolve: {
|
||||
projectResolver: BaseRoutingResolver
|
||||
},
|
||||
children: [
|
||||
{ path: 'repository', component: RepositoryComponent },
|
||||
{ path: 'replication', component: ReplicationComponent },
|
||||
{ path: 'member', component: MemberComponent },
|
||||
{
|
||||
path: 'member', component: MemberComponent,
|
||||
resolve: {
|
||||
memberResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{ path: 'log', component: AuditLogComponent }
|
||||
]
|
||||
}
|
||||
@ -33,6 +53,6 @@ const projectRoutes: Routes = [
|
||||
imports: [
|
||||
RouterModule.forChild(projectRoutes)
|
||||
],
|
||||
exports: [ RouterModule ]
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ProjectRoutingModule {}
|
||||
export class ProjectRoutingModule { }
|
@ -14,10 +14,11 @@ import { ListProjectComponent } from './list-project/list-project.component';
|
||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||
|
||||
import { MemberComponent } from './member/member.component';
|
||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||
import { ProjectRoutingModule } from './project-routing.module';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import { DATAGRID_DIRECTIVES } from 'clarity-angular';
|
||||
import { MemberService } from './member/member.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -35,10 +36,11 @@ import { DATAGRID_DIRECTIVES } from 'clarity-angular';
|
||||
ActionProjectComponent,
|
||||
ListProjectComponent,
|
||||
ProjectDetailComponent,
|
||||
MemberComponent
|
||||
MemberComponent,
|
||||
AddMemberComponent
|
||||
],
|
||||
exports: [ ListProjectComponent ],
|
||||
providers: [ ProjectService ]
|
||||
providers: [ ProjectService, MemberService ]
|
||||
})
|
||||
export class ProjectModule {
|
||||
|
||||
|
51
src/ui/static/app/shared/max-length-ext.directive.ts
Normal file
51
src/ui/static/app/shared/max-length-ext.directive.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Directive, OnChanges, Input, SimpleChanges } from '@angular/core';
|
||||
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms';
|
||||
|
||||
export const assiiChars = /[\u4e00-\u9fa5]/;
|
||||
|
||||
export function maxLengthExtValidator(length: number): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: any } => {
|
||||
const value: string = control.value
|
||||
if (!value || value.trim() === "") {
|
||||
return { 'maxLengthExt': 0 };
|
||||
}
|
||||
|
||||
const regExp = new RegExp(assiiChars, 'i');
|
||||
let count = 0;
|
||||
let len = value.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (regExp.test(value[i])) {
|
||||
count += 2;
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > length ? { 'maxLengthExt': count } : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[maxLengthExt]',
|
||||
providers: [{ provide: NG_VALIDATORS, useExisting: MaxLengthExtValidatorDirective, multi: true }]
|
||||
})
|
||||
|
||||
export class MaxLengthExtValidatorDirective implements Validator, OnChanges {
|
||||
@Input() maxLengthExt: number;
|
||||
private valFn = Validators.nullValidator;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const change = changes['maxLengthExt'];
|
||||
if (change) {
|
||||
const val: number = change.currentValue;
|
||||
this.valFn = maxLengthExtValidator(val);
|
||||
} else {
|
||||
this.valFn = Validators.nullValidator;
|
||||
}
|
||||
console.info(changes, this.maxLengthExt);
|
||||
}
|
||||
|
||||
validate(control: AbstractControl): { [key: string]: any } {
|
||||
return this.valFn(control);
|
||||
}
|
||||
}
|
11
src/ui/static/app/shared/session-user.ts
Normal file
11
src/ui/static/app/shared/session-user.ts
Normal file
@ -0,0 +1,11 @@
|
||||
//Define the session user
|
||||
export class SessionUser {
|
||||
user_id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
realname: string;
|
||||
role_name?: string;
|
||||
role_id?: number;
|
||||
has_admin_role?: number;
|
||||
comment: string;
|
||||
}
|
@ -1,8 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Headers, Http } from '@angular/http';
|
||||
import { Headers, Http, URLSearchParams } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
const currentUserEndpint = "/api/users/current";
|
||||
import { SessionUser } from './session-user';
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
|
||||
const urlPrefix = '';
|
||||
const signInUrl = urlPrefix + '/login';
|
||||
const currentUserEndpint = urlPrefix + "/api/users/current";
|
||||
const signOffEndpoint = urlPrefix + "/log_out";
|
||||
const accountEndpoint = urlPrefix + "/api/users/:id";
|
||||
|
||||
/**
|
||||
* Define related methods to handle account and session corresponding things
|
||||
*
|
||||
@ -11,33 +19,92 @@ const currentUserEndpint = "/api/users/current";
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
currentUser: any = null;
|
||||
currentUser: SessionUser = null;
|
||||
|
||||
private headers = new Headers({
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
|
||||
private formHeaders = 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.formHeaders })
|
||||
.toPromise()
|
||||
.then(()=>null)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related information of current signed in user from backend
|
||||
*
|
||||
* @returns {Promise<any>}
|
||||
* @returns {Promise<SessionUser>}
|
||||
*
|
||||
* @memberOf SessionService
|
||||
*/
|
||||
retrieveUser(): Promise<any> {
|
||||
retrieveUser(): Promise<SessionUser> {
|
||||
return this.http.get(currentUserEndpint, { headers: this.headers }).toPromise()
|
||||
.then(response => this.currentUser = response.json())
|
||||
.catch(error => {
|
||||
console.log("An error occurred when getting current user ", error);//TODO: Will replaced with general error handler
|
||||
.then(response => {
|
||||
this.currentUser = response.json() as SessionUser;
|
||||
return this.currentUser;
|
||||
})
|
||||
.catch(error => this.handleError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* For getting info
|
||||
*/
|
||||
getCurrentUser(): any {
|
||||
getCurrentUser(): SessionUser {
|
||||
return this.currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out the system
|
||||
*/
|
||||
signOff(): Promise<any> {
|
||||
return this.http.get(signOffEndpoint, { headers: this.headers }).toPromise()
|
||||
.then(() => {
|
||||
//Destroy current session cache
|
||||
this.currentUser = null;
|
||||
}) //Nothing returned
|
||||
.catch(error => this.handleError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Update accpunt settings
|
||||
*
|
||||
* @param {SessionUser} account
|
||||
* @returns {Promise<any>}
|
||||
*
|
||||
* @memberOf SessionService
|
||||
*/
|
||||
updateAccountSettings(account: SessionUser): Promise<any>{
|
||||
if(!account){
|
||||
return Promise.reject("Invalid account settings");
|
||||
}
|
||||
console.info(account);
|
||||
let putUrl = accountEndpoint.replace(":id", account.user_id+"");
|
||||
return this.http.put(putUrl, JSON.stringify(account), { headers: this.headers }).toPromise()
|
||||
.then(() => {
|
||||
//Retrieve current session user
|
||||
return this.retrieveUser();
|
||||
})
|
||||
.catch(error => this.handleError(error))
|
||||
}
|
||||
}
|
@ -1,20 +1,26 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreModule } from '../core/core.module';
|
||||
import { AccountModule } from '../account/account.module';
|
||||
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { MessageComponent } from '../global-message/message.component';
|
||||
import { MessageService } from '../global-message/message.service';
|
||||
import { MaxLengthExtValidatorDirective } from './max-length-ext.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
CoreModule,
|
||||
AccountModule
|
||||
],
|
||||
declarations: [
|
||||
MessageComponent
|
||||
MessageComponent,
|
||||
MaxLengthExtValidatorDirective
|
||||
],
|
||||
exports: [
|
||||
CoreModule,
|
||||
MessageComponent
|
||||
AccountModule,
|
||||
MessageComponent,
|
||||
MaxLengthExtValidatorDirective
|
||||
],
|
||||
providers: [SessionService, MessageService]
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user