mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Added lastest UI codes.
This commit is contained in:
parent
23f0ff1ea5
commit
5619e4e7e3
@ -1,6 +1,6 @@
|
|||||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalSize]="'lg'">
|
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalSize]="'lg'">
|
||||||
<h3 class="modal-title">Accout Settings</h3>
|
<h3 class="modal-title">User Profile</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body" style="overflow-y: hidden;">
|
||||||
<form #accountSettingsFrom="ngForm" class="form">
|
<form #accountSettingsFrom="ngForm" class="form">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="account_settings_full_name" class="col-md-4 required">Full name</label>
|
<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)">
|
<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">
|
<input type="text" name="account_settings_full_name" #fullNameInput="ngModel" [(ngModel)]="account.realname" required maxLengthExt="20" id="account_settings_full_name" size="48">
|
||||||
<span class="tooltip-content">
|
<span class="tooltip-content">
|
||||||
Max length of full name is 20
|
Max length of full name is 20
|
||||||
</span>
|
</span>
|
||||||
@ -29,14 +29,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="account_settings_comments" class="col-md-4">Comments</label>
|
<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">
|
<label for="account_settings_comments" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-right" [class.invalid]="commentInput.invalid && (commentInput.dirty || commentInput.touched)">
|
||||||
|
<input type="text" #commentInput="ngModel" maxLengthExt="20" name="account_settings_comments" [(ngModel)]="account.comment" id="account_settings_comments" size="48">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Length of comment should be less than 20
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [hidden]='errorMessage === ""'>
|
<div style="height: 30px;"></div>
|
||||||
|
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [(clrAlertClosed)]="alertClose">
|
||||||
<div class="alert-item">
|
<div class="alert-item">
|
||||||
<span class="alert-text">
|
<span class="alert-text">
|
||||||
This alert indicates success.
|
{{errorMessage}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</clr-alert>
|
</clr-alert>
|
||||||
|
@ -14,6 +14,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
staticBackdrop: boolean = true;
|
staticBackdrop: boolean = true;
|
||||||
account: SessionUser;
|
account: SessionUser;
|
||||||
error: any;
|
error: any;
|
||||||
|
alertClose: boolean = true;
|
||||||
|
|
||||||
private isOnCalling: boolean = false;
|
private isOnCalling: boolean = false;
|
||||||
private formValueChanged: boolean = false;
|
private formValueChanged: boolean = false;
|
||||||
@ -37,7 +38,16 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get errorMessage(): string {
|
public get errorMessage(): string {
|
||||||
return this.error ? (this.error.message ? this.error.message : this.error) : "";
|
if(this.error){
|
||||||
|
if(this.error.message){
|
||||||
|
return this.error.message;
|
||||||
|
}else{
|
||||||
|
if(this.error._body){
|
||||||
|
return this.error._body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
@ -85,7 +95,8 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.isOnCalling = false;
|
this.isOnCalling = false;
|
||||||
this.error = error
|
this.error = error;
|
||||||
|
this.alertClose = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,13 +5,15 @@ import { CoreModule } from '../core/core.module';
|
|||||||
import { SignInComponent } from './sign-in/sign-in.component';
|
import { SignInComponent } from './sign-in/sign-in.component';
|
||||||
import { PasswordSettingComponent } from './password/password-setting.component';
|
import { PasswordSettingComponent } from './password/password-setting.component';
|
||||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
import { PasswordSettingService } from './password/password-setting.service';
|
import { PasswordSettingService } from './password/password-setting.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
RouterModule
|
RouterModule,
|
||||||
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
declarations: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||||
exports: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
exports: [SignInComponent, PasswordSettingComponent, AccountSettingsModalComponent],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ViewChild, AfterViewChecked, Output, EventEmitter } from '@angular/core';
|
import { Component, ViewChild, AfterViewChecked } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
@ -20,9 +20,6 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
|
|
||||||
pwdFormRef: NgForm;
|
pwdFormRef: NgForm;
|
||||||
@ViewChild("changepwdForm") pwdForm: NgForm;
|
@ViewChild("changepwdForm") pwdForm: NgForm;
|
||||||
|
|
||||||
@Output() private pwdChange = new EventEmitter<any>();
|
|
||||||
|
|
||||||
constructor(private passwordService: PasswordSettingService, private session: SessionService){}
|
constructor(private passwordService: PasswordSettingService, private session: SessionService){}
|
||||||
|
|
||||||
//If form is valid
|
//If form is valid
|
||||||
@ -90,9 +87,6 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.onCalling = false;
|
this.onCalling = false;
|
||||||
//Tell shell to reset current view
|
|
||||||
this.pwdChange.emit(true);
|
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -31,8 +31,7 @@
|
|||||||
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
|
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
|
||||||
Invalid user name or password
|
Invalid user name or password
|
||||||
</div>
|
</div>
|
||||||
<button [class.visibility-hidden]="signInStatus === statusOnGoing" [disabled]="signInStatus === statusOnGoing" type="submit" class="btn btn-primary" (click)="signIn()">LOG IN</button>
|
<button [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>
|
<a href="javascript:void(0)" class="signup" (click)="signUp()">Sign up for an account</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -6,9 +6,9 @@ import { ClarityModule } from 'clarity-angular';
|
|||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
import { BaseModule } from './base/base.module';
|
import { BaseModule } from './base/base.module';
|
||||||
|
|
||||||
import { HarborRoutingModule } from './harbor-routing.module';
|
import { HarborRoutingModule } from './harbor-routing.module';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
|
import { AccountModule } from './account/account.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -17,6 +17,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
SharedModule,
|
SharedModule,
|
||||||
BaseModule,
|
BaseModule,
|
||||||
|
AccountModule,
|
||||||
HarborRoutingModule
|
HarborRoutingModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -4,6 +4,7 @@ import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
|||||||
|
|
||||||
import { DashboardComponent } from '../dashboard/dashboard.component';
|
import { DashboardComponent } from '../dashboard/dashboard.component';
|
||||||
import { ProjectComponent } from '../project/project.component';
|
import { ProjectComponent } from '../project/project.component';
|
||||||
|
import { UserComponent } from '../user/user.component';
|
||||||
|
|
||||||
import { BaseRoutingResolver } from './base-routing-resolver.service';
|
import { BaseRoutingResolver } from './base-routing-resolver.service';
|
||||||
|
|
||||||
@ -19,6 +20,13 @@ const baseRoutes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'projects',
|
path: 'projects',
|
||||||
component: ProjectComponent
|
component: ProjectComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users',
|
||||||
|
component: UserComponent,
|
||||||
|
resolve: {
|
||||||
|
projectsResolver: BaseRoutingResolver
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}];
|
}];
|
||||||
|
@ -4,6 +4,7 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { DashboardModule } from '../dashboard/dashboard.module';
|
import { DashboardModule } from '../dashboard/dashboard.module';
|
||||||
import { ProjectModule } from '../project/project.module';
|
import { ProjectModule } from '../project/project.module';
|
||||||
import { UserModule } from '../user/user.module';
|
import { UserModule } from '../user/user.module';
|
||||||
|
import { AccountModule } from '../account/account.module';
|
||||||
|
|
||||||
import { NavigatorComponent } from './navigator/navigator.component';
|
import { NavigatorComponent } from './navigator/navigator.component';
|
||||||
import { GlobalSearchComponent } from './global-search/global-search.component';
|
import { GlobalSearchComponent } from './global-search/global-search.component';
|
||||||
@ -19,7 +20,8 @@ import { BaseRoutingModule } from './base-routing.module';
|
|||||||
DashboardModule,
|
DashboardModule,
|
||||||
ProjectModule,
|
ProjectModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
BaseRoutingModule
|
BaseRoutingModule,
|
||||||
|
AccountModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
NavigatorComponent,
|
NavigatorComponent,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<input id="tabsystem" type="checkbox">
|
<input id="tabsystem" type="checkbox">
|
||||||
<label for="tabsystem">System Managements</label>
|
<label for="tabsystem">System Managements</label>
|
||||||
<ul class="nav-list">
|
<ul class="nav-list">
|
||||||
<li><a class="nav-link">Users</a></li>
|
<li><a class="nav-link" routerLink="/harbor/users" routerLinkActive="active">Users</a></li>
|
||||||
<li><a class="nav-link">Replications</a></li>
|
<li><a class="nav-link">Replications</a></li>
|
||||||
<li><a class="nav-link">Quarantine[*]</a></li>
|
<li><a class="nav-link">Quarantine[*]</a></li>
|
||||||
<li><a class="nav-link">Configurations[*]</a></li>
|
<li><a class="nav-link">Configurations[*]</a></li>
|
||||||
@ -27,4 +27,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</clr-main-container>
|
</clr-main-container>
|
||||||
<account-settings-modal></account-settings-modal>
|
<account-settings-modal></account-settings-modal>
|
||||||
<password-setting (pwdChange)="watchPwdChange($event)"></password-setting>
|
<password-setting></password-setting>
|
@ -77,11 +77,4 @@ export class HarborShellComponent implements OnInit {
|
|||||||
this.isSearchResultsOpened = false;
|
this.isSearchResultsOpened = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Watch password whether changed
|
|
||||||
watchPwdChange(event: any): void {
|
|
||||||
if (event) {
|
|
||||||
this.navigator.logOut(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -12,19 +12,6 @@
|
|||||||
<span class="custom-divider"></span>
|
<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>
|
<a href="javascript:void(0)" class="nav-link nav-text sign-up-override" routerLink="/sign-up" routerLinkActive="active">Sign Up</a>
|
||||||
</div>
|
</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 (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">
|
<clr-dropdown class="dropdown bottom-left">
|
||||||
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
<button class="nav-icon" clrDropdownToggle style="width: 90px;">
|
||||||
<clr-icon shape="world" style="left:-5px;"></clr-icon>
|
<clr-icon shape="world" style="left:-5px;"></clr-icon>
|
||||||
@ -37,15 +24,18 @@
|
|||||||
<a href="javascript:void(0)" clrDropdownItem>中文繁體</a>
|
<a href="javascript:void(0)" clrDropdownItem>中文繁體</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
<clr-dropdown class="dropdown bottom-right">
|
<clr-dropdown [clrMenuPosition]="'bottom-right'" class="dropdown" *ngIf="isSessionValid">
|
||||||
<button class="nav-icon" clrDropdownToggle>
|
<button class="nav-text" clrDropdownToggle>
|
||||||
<clr-icon shape="cog"></clr-icon>
|
<clr-icon shape="user" class="is-inverse" size="24" style="left: -2px;"></clr-icon>
|
||||||
|
<span>{{accountName}}</span>
|
||||||
<clr-icon shape="caret down"></clr-icon>
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a href="javascript:void(0)" clrDropdownItem>Preferences</a>
|
<a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">User Profile</a>
|
||||||
<a href="javascript:void(0)" clrDropdownItem>Add User</a>
|
<a href="javascript:void(0)" clrDropdownItem (click)="openChangePwdModal()">Change Password</a>
|
||||||
<a href="javascript:void(0)" clrDropdownItem>About</a>
|
<a href="javascript:void(0)" clrDropdownItem>About</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="javascript:void(0)" clrDropdownItem (click)="logOut()">Log out</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,6 +32,10 @@ export class NavigatorComponent implements OnInit {
|
|||||||
return this.sessionUser != null;
|
return this.sessionUser != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get accountName(): string {
|
||||||
|
return this.sessionUser?this.sessionUser.username: "";
|
||||||
|
}
|
||||||
|
|
||||||
//Open the account setting dialog
|
//Open the account setting dialog
|
||||||
openAccountSettingsModal(): void {
|
openAccountSettingsModal(): void {
|
||||||
this.showAccountSettingsModal.emit({
|
this.showAccountSettingsModal.emit({
|
||||||
@ -54,17 +58,12 @@ export class NavigatorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Log out system
|
//Log out system
|
||||||
logOut(reSignIn: boolean): void {
|
logOut(): void {
|
||||||
this.session.signOff()
|
this.session.signOff()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.sessionUser = null;
|
this.sessionUser = null;
|
||||||
if (reSignIn) {
|
|
||||||
//Naviagte to the sign in route
|
//Naviagte to the sign in route
|
||||||
this.router.navigate(["/sign-in"]);
|
this.router.navigate(["/sign-in"]);
|
||||||
} else {
|
|
||||||
//Naviagte to the default route
|
|
||||||
this.router.navigate(["/harbor"]);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch()//TODO:
|
.catch()//TODO:
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,28 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-right">
|
||||||
<div class="col-md-2 push-md-8">
|
<div class="col-xs-3 push-md-2 flex-xs-middle">
|
||||||
<input type="text" placeholder="Search for user">
|
<button class="btn btn-link" (click)="toggleOptionalName(currentOption)">{{toggleName[currentOption]}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 flex-xs-middle">
|
||||||
|
<clr-icon shape="filter" style="position: relative; left: 15px;"></clr-icon><input style="padding-left: 20px;" type="text" placeholder="Filter logs" #searchUsername (keyup.enter)="doSearchAuditLogs(searchUsername.value)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row flex-items-xs-right advance-option" [hidden]="currentOption === 0">
|
||||||
|
<div class="col-xs-2 push-md-1">
|
||||||
|
<clr-dropdown [clrMenuPosition]="'bottom-left'" >
|
||||||
|
<button class="btn btn-link" clrDropdownToggle>
|
||||||
|
All Operations
|
||||||
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a href="javascript:void(0)" clrDropdownItem *ngFor="let f of filterOptions" (click)="toggleFilterOption(f.key)"><clr-icon shape="check" [hidden]="!f.checked"></clr-icon> {{f.description}}</a>
|
||||||
|
</div>
|
||||||
|
</clr-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5 push-md-1">
|
||||||
|
<clr-icon shape="date"></clr-icon><input type="date" #fromTime (change)="doSearchByTimeRange(fromTime.value, 'begin')">
|
||||||
|
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doSearchByTimeRange(toTime.value, 'end')">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
@ -13,12 +33,12 @@
|
|||||||
<clr-dg-column>Timestamp</clr-dg-column>
|
<clr-dg-column>Timestamp</clr-dg-column>
|
||||||
<clr-dg-row *ngFor="let l of auditLogs">
|
<clr-dg-row *ngFor="let l of auditLogs">
|
||||||
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.repoName}}</clr-dg-cell>
|
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.tag}}</clr-dg-cell>
|
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.timestamp}}</clr-dg-cell>
|
<clr-dg-cell>{{l.op_time}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{auditLogs.length}} item(s)</clr-dg-footer>
|
<clr-dg-footer>{{ (auditLogs ? auditLogs.length : 0) }} item(s)</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,18 +1,138 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
|
|
||||||
import { AuditLog } from './audit-log';
|
import { AuditLog } from './audit-log';
|
||||||
|
import { SessionUser } from '../shared/session-user';
|
||||||
|
|
||||||
|
import { AuditLogService } from './audit-log.service';
|
||||||
|
import { SessionService } from '../shared/session.service';
|
||||||
|
import { MessageService } from '../global-message/message.service';
|
||||||
|
|
||||||
|
export const optionalSearch: {} = {0: 'Advanced', 1: 'Simple'};
|
||||||
|
|
||||||
|
|
||||||
|
export class FilterOption {
|
||||||
|
key: string;
|
||||||
|
description: string;
|
||||||
|
checked: boolean;
|
||||||
|
|
||||||
|
constructor(private iKey: string, private iDescription: string, private iChecked: boolean) {
|
||||||
|
this.key = iKey;
|
||||||
|
this.description = iDescription;
|
||||||
|
this.checked = iChecked;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return 'key:' + this.key + ', description:' + this.description + ', checked:' + this.checked + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './audit-log.component.html'
|
selector: 'audit-log',
|
||||||
|
templateUrl: './audit-log.component.html',
|
||||||
|
styleUrls: [ 'audit-log.css' ]
|
||||||
})
|
})
|
||||||
export class AuditLogComponent implements OnInit {
|
export class AuditLogComponent implements OnInit {
|
||||||
|
|
||||||
|
currentUser: SessionUser;
|
||||||
|
projectId: number;
|
||||||
|
queryParam: AuditLog = new AuditLog();
|
||||||
auditLogs: AuditLog[];
|
auditLogs: AuditLog[];
|
||||||
|
|
||||||
ngOnInit(): void {
|
toggleName = optionalSearch;
|
||||||
this.auditLogs = [
|
currentOption: number = 0;
|
||||||
{ username: 'Admin', repoName: 'project01', tag: '', operation: 'create', timestamp: '2016-12-23 12:05:17' },
|
filterOptions: FilterOption[] = [
|
||||||
{ username: 'Admin', repoName: 'project01/ubuntu', tag: '14.04', operation: 'push', timestamp: '2016-12-30 14:52:23' },
|
new FilterOption('all', 'All Operations', true),
|
||||||
{ username: 'user1', repoName: 'project01/mysql', tag: '5.6', operation: 'pull', timestamp: '2016-12-30 12:12:33' }
|
new FilterOption('pull', 'Pull', true),
|
||||||
|
new FilterOption('push', 'Push', true),
|
||||||
|
new FilterOption('create', 'Create', true),
|
||||||
|
new FilterOption('delete', 'Delete', true),
|
||||||
|
new FilterOption('others', 'Others', true)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private router: Router, private auditLogService: AuditLogService, private messageService: MessageService) {
|
||||||
|
//Get current user from registered resolver.
|
||||||
|
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['auditLogResolver']);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||||
|
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||||
|
this.queryParam.project_id = this.projectId;
|
||||||
|
this.retrieve(this.queryParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieve(queryParam: AuditLog): void {
|
||||||
|
this.auditLogService
|
||||||
|
.listAuditLogs(queryParam)
|
||||||
|
.subscribe(
|
||||||
|
response=>this.auditLogs = response,
|
||||||
|
error=>{
|
||||||
|
this.router.navigate(['/harbor', 'projects']);
|
||||||
|
this.messageService.announceMessage('Failed to list audit logs with project ID:' + queryParam.project_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearchAuditLogs(searchUsername: string): void {
|
||||||
|
this.queryParam.username = searchUsername;
|
||||||
|
this.retrieve(this.queryParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearchByTimeRange(strDate: string, target: string): void {
|
||||||
|
let oneDayOffset = 3600 * 24;
|
||||||
|
switch(target) {
|
||||||
|
case 'begin':
|
||||||
|
this.queryParam.begin_timestamp = new Date(strDate).getTime() / 1000;
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
this.queryParam.end_timestamp = new Date(strDate).getTime() / 1000 + oneDayOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log('Search audit log filtered by time range, begin: ' + this.queryParam.begin_timestamp + ', end:' + this.queryParam.end_timestamp);
|
||||||
|
this.retrieve(this.queryParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearchByOptions() {
|
||||||
|
let selectAll = true;
|
||||||
|
let operationFilter: string[] = [];
|
||||||
|
for(var i in this.filterOptions) {
|
||||||
|
let filterOption = this.filterOptions[i];
|
||||||
|
if(filterOption.checked) {
|
||||||
|
operationFilter.push(this.filterOptions[i].key);
|
||||||
|
}else{
|
||||||
|
selectAll = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(selectAll) {
|
||||||
|
operationFilter = [];
|
||||||
|
}
|
||||||
|
this.queryParam.keywords = operationFilter.join('/');
|
||||||
|
this.retrieve(this.queryParam);
|
||||||
|
console.log('Search option filter:' + operationFilter.join('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOptionalName(option: number): void {
|
||||||
|
(option === 1) ? this.currentOption = 0 : this.currentOption = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFilterOption(option: string): void {
|
||||||
|
let selectedOption = this.filterOptions.find(value =>(value.key === option));
|
||||||
|
selectedOption.checked = !selectedOption.checked;
|
||||||
|
if(selectedOption.key === 'all') {
|
||||||
|
this.filterOptions.filter(value=> value.key !== selectedOption.key).forEach(value => value.checked = selectedOption.checked);
|
||||||
|
} else {
|
||||||
|
if(!selectedOption.checked) {
|
||||||
|
this.filterOptions.find(value=>value.key === 'all').checked = false;
|
||||||
|
}
|
||||||
|
let selectAll = true;
|
||||||
|
this.filterOptions.filter(value=> value.key !== 'all').forEach(value =>{
|
||||||
|
if(!value.checked) {
|
||||||
|
selectAll = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.filterOptions.find(value=>value.key === 'all').checked = selectAll;
|
||||||
|
}
|
||||||
|
this.doSearchByOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
3
src/clarity-seed/src/app/log/audit-log.css
Normal file
3
src/clarity-seed/src/app/log/audit-log.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.advance-option {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
35
src/clarity-seed/src/app/log/audit-log.service.ts
Normal file
35
src/clarity-seed/src/app/log/audit-log.service.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http, Headers, RequestOptions } from '@angular/http';
|
||||||
|
|
||||||
|
import { BaseService } from '../service/base.service';
|
||||||
|
|
||||||
|
import { AuditLog } from './audit-log';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/catch';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/observable/throw';
|
||||||
|
|
||||||
|
export const urlPrefix = '';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuditLogService extends BaseService {
|
||||||
|
|
||||||
|
constructor(private http: Http) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
listAuditLogs(queryParam: AuditLog): Observable<AuditLog[]> {
|
||||||
|
return this.http
|
||||||
|
.post(urlPrefix + `/api/projects/${queryParam.project_id}/logs/filter`, {
|
||||||
|
begin_timestamp: queryParam.begin_timestamp,
|
||||||
|
end_timestamp: queryParam.end_timestamp,
|
||||||
|
keywords: queryParam.keywords,
|
||||||
|
operation: queryParam.operation,
|
||||||
|
project_id: queryParam.project_id,
|
||||||
|
username: queryParam.username })
|
||||||
|
.map(response=>response.json() as AuditLog[])
|
||||||
|
.catch(error=>this.handleError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,30 @@
|
|||||||
|
/*
|
||||||
|
{
|
||||||
|
"log_id": 3,
|
||||||
|
"user_id": 0,
|
||||||
|
"project_id": 0,
|
||||||
|
"repo_name": "library/mysql",
|
||||||
|
"repo_tag": "5.6",
|
||||||
|
"guid": "",
|
||||||
|
"operation": "push",
|
||||||
|
"op_time": "2017-02-14T09:22:58Z",
|
||||||
|
"username": "admin",
|
||||||
|
"keywords": "",
|
||||||
|
"BeginTime": "0001-01-01T00:00:00Z",
|
||||||
|
"begin_timestamp": 0,
|
||||||
|
"EndTime": "0001-01-01T00:00:00Z",
|
||||||
|
"end_timestamp": 0
|
||||||
|
}
|
||||||
|
*/
|
||||||
export class AuditLog {
|
export class AuditLog {
|
||||||
|
log_id: number;
|
||||||
|
project_id: number;
|
||||||
username: string;
|
username: string;
|
||||||
repoName: string;
|
repo_name: string;
|
||||||
tag: string;
|
repo_tag: string;
|
||||||
operation: string;
|
operation: string;
|
||||||
timestamp: string;
|
op_time: Date;
|
||||||
|
begin_timestamp: number = 0;
|
||||||
|
end_timestamp: number = 0;
|
||||||
|
keywords: string;
|
||||||
}
|
}
|
@ -1,10 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AuditLogComponent } from './audit-log.component';
|
import { AuditLogComponent } from './audit-log.component';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { AuditLogService } from './audit-log.service';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ SharedModule ],
|
imports: [ SharedModule ],
|
||||||
declarations: [ AuditLogComponent ],
|
declarations: [ AuditLogComponent ],
|
||||||
|
providers: [ AuditLogService ],
|
||||||
exports: [ AuditLogComponent ]
|
exports: [ AuditLogComponent ]
|
||||||
})
|
})
|
||||||
export class LogModule {}
|
export class LogModule {}
|
@ -37,9 +37,11 @@ export class CreateProjectComponent {
|
|||||||
if (error instanceof Response) {
|
if (error instanceof Response) {
|
||||||
switch(error.status) {
|
switch(error.status) {
|
||||||
case 409:
|
case 409:
|
||||||
this.errorMessage = 'Project name already exists.'; break;
|
this.errorMessage = 'Project name already exists.';
|
||||||
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
this.errorMessage = 'Project name is illegal.'; break;
|
this.errorMessage = 'Project name is illegal.';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
this.errorMessage = 'Unknown error for project name.';
|
this.errorMessage = 'Unknown error for project name.';
|
||||||
this.messageService.announceMessage(this.errorMessage);
|
this.messageService.announceMessage(this.errorMessage);
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
|
||||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
|
||||||
{{currentType.value}}
|
|
||||||
<clr-icon shape="caret down"></clr-icon>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let p of types" (click)="doFilter(p.key)">{{p.value}}</a>
|
|
||||||
</div>
|
|
||||||
</clr-dropdown>
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Component, Output, EventEmitter } from '@angular/core';
|
|
||||||
|
|
||||||
export const projectTypes = [
|
|
||||||
{ 'key' : 0, 'value': 'My Projects' },
|
|
||||||
{ 'key' : 1, 'value': 'Public Projects'}
|
|
||||||
];
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'filter-project',
|
|
||||||
templateUrl: 'filter-project.component.html'
|
|
||||||
})
|
|
||||||
export class FilterProjectComponent {
|
|
||||||
|
|
||||||
@Output() filter = new EventEmitter<number>();
|
|
||||||
types = projectTypes;
|
|
||||||
currentType = projectTypes[0];
|
|
||||||
|
|
||||||
doFilter(type: number) {
|
|
||||||
console.log('Filtered projects by:' + type);
|
|
||||||
this.currentType = projectTypes.find(item=>item.key === type);
|
|
||||||
this.filter.emit(type);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
<clr-datagrid [(clrDgSelected)]="selected">
|
<clr-datagrid>
|
||||||
<clr-dg-column>Name</clr-dg-column>
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
<clr-dg-column>Public/Private</clr-dg-column>
|
<clr-dg-column>Public/Private</clr-dg-column>
|
||||||
<clr-dg-column>Repositories</clr-dg-column>
|
<clr-dg-column>Repositories</clr-dg-column>
|
||||||
<clr-dg-column>Creation time</clr-dg-column>
|
<clr-dg-column>Creation time</clr-dg-column>
|
||||||
<clr-dg-column>Description</clr-dg-column>
|
<clr-dg-column>Description</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let p of projects" [clrDgItem]="p" [(clrDgSelected)]="p.selected">
|
<clr-dg-row *clrDgItems="let p of projects" [clrDgItem]="p">
|
||||||
<!--<clr-dg-action-overflow>
|
<!--<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="onEdit(p)">Edit</button>
|
<button class="action-item" (click)="onEdit(p)">Edit</button>
|
||||||
<button class="action-item" (click)="onDelete(p)">Delete</button>
|
<button class="action-item" (click)="onDelete(p)">Delete</button>
|
||||||
@ -19,9 +19,6 @@
|
|||||||
<action-project (togglePublic)="toggleProject($event)" (deleteProject)="deleteProject($event)" [project]="p"></action-project>
|
<action-project (togglePublic)="toggleProject($event)" (deleteProject)="deleteProject($event)" [project]="p"></action-project>
|
||||||
</span>
|
</span>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{ (projects ? projects.length : 0) }} item(s)</clr-dg-footer>
|
<clr-dg-footer>{{ (projects ? projects.length : 0) }} item(s)</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
import { ProjectService } from '../project.service';
|
import { ProjectService } from '../project.service';
|
||||||
|
|
||||||
@ -9,53 +9,17 @@ import { ProjectService } from '../project.service';
|
|||||||
})
|
})
|
||||||
export class ListProjectComponent {
|
export class ListProjectComponent {
|
||||||
|
|
||||||
projects: Project[];
|
@Input() projects: Project[];
|
||||||
errorMessage: string;
|
|
||||||
|
|
||||||
selected = [];
|
@Output() toggle = new EventEmitter<Project>();
|
||||||
|
@Output() delete = new EventEmitter<Project>();
|
||||||
@Output() actionPerform = new EventEmitter<boolean>();
|
|
||||||
|
|
||||||
constructor(private projectService: ProjectService) {}
|
|
||||||
|
|
||||||
retrieve(name: string, isPublic: number): void {
|
|
||||||
this.projectService
|
|
||||||
.listProjects(name, isPublic)
|
|
||||||
.subscribe(
|
|
||||||
response => this.projects = response,
|
|
||||||
error => this.errorMessage = <any>error);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleProject(p: Project) {
|
toggleProject(p: Project) {
|
||||||
this.projectService
|
this.toggle.emit(p);
|
||||||
.toggleProjectPublic(p.project_id, p.public)
|
|
||||||
.subscribe(
|
|
||||||
response=>console.log(response),
|
|
||||||
error=>console.log(error)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProject(p: Project) {
|
deleteProject(p: Project) {
|
||||||
this.projectService
|
this.delete.emit(p);
|
||||||
.deleteProject(p.project_id)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
console.log(response);
|
|
||||||
this.actionPerform.emit(true);
|
|
||||||
},
|
|
||||||
error=>console.log(error)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSelectedProjects() {
|
|
||||||
this.selected.forEach(p=>this.deleteProject(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
onEdit(p: Project) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onDelete(p: Project) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4 flex-xs-middle">
|
||||||
<button class="btn btn-sm" (click)="openAddMemberModal()">new user</button>
|
<button class="btn btn-link" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> new user</button>
|
||||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4 flex-xs-middle">
|
||||||
<input type="text" placeholder="Search for users" #searchMember (keyup.enter)="doSearch(searchMember.value)">
|
<clr-icon shape="filter" style="position: relative; left: 15px;"></clr-icon><input style="padding-left: 20px;" type="text" placeholder="Search for users" #searchMember (keyup.enter)="doSearch(searchMember.value)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
|
|
||||||
import { SessionUser } from '../../shared/session-user';
|
import { SessionUser } from '../../shared/session-user';
|
||||||
import { Member } from './member';
|
import { Member } from './member';
|
||||||
@ -30,7 +30,7 @@ export class MemberComponent implements OnInit {
|
|||||||
@ViewChild(AddMemberComponent)
|
@ViewChild(AddMemberComponent)
|
||||||
addMemberComponent: AddMemberComponent;
|
addMemberComponent: AddMemberComponent;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private memberService: MemberService, private messageService: MessageService) {
|
constructor(private route: ActivatedRoute, private router: Router, private memberService: MemberService, private messageService: MessageService) {
|
||||||
//Get current user from registered resolver.
|
//Get current user from registered resolver.
|
||||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
||||||
}
|
}
|
||||||
@ -40,7 +40,10 @@ export class MemberComponent implements OnInit {
|
|||||||
.listMembers(projectId, username)
|
.listMembers(projectId, username)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>this.members = response,
|
response=>this.members = response,
|
||||||
error=>console.log(error)
|
error=>{
|
||||||
|
this.router.navigate(['/harbor', 'projects']);
|
||||||
|
this.messageService.announceMessage('Failed to get project member with project ID:' + projectId);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h1 class="display-in-line">Project 01</h1><h6 class="display-in-line project-title">PROJECT</h6>
|
<h1 class="display-in-line">{{currentProject.name}}</h1>
|
||||||
<nav class="subnav">
|
<nav class="subnav">
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Project } from '../project';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'project-detail',
|
selector: 'project-detail',
|
||||||
templateUrl: "project-detail.component.html",
|
templateUrl: "project-detail.component.html",
|
||||||
styleUrls: [ 'project-detail.css' ]
|
styleUrls: [ 'project-detail.css' ]
|
||||||
})
|
})
|
||||||
export class ProjectDetailComponent {}
|
export class ProjectDetailComponent {
|
||||||
|
|
||||||
|
currentProject: Project;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private router: Router) {
|
||||||
|
this.route.data.subscribe(data=>this.currentProject = <Project>data['projectResolver']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Project } from './project';
|
||||||
|
import { ProjectService } from './project.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProjectRoutingResolver implements Resolve<Project>{
|
||||||
|
|
||||||
|
constructor(private projectService: ProjectService, private router: Router) {}
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Project> {
|
||||||
|
let projectId = route.params['id'];
|
||||||
|
return this.projectService
|
||||||
|
.getProject(projectId)
|
||||||
|
.then(project=> {
|
||||||
|
if(project) {
|
||||||
|
return project;
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/harbor', 'projects']);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import { MemberComponent } from './member/member.component';
|
|||||||
import { AuditLogComponent } from '../log/audit-log.component';
|
import { AuditLogComponent } from '../log/audit-log.component';
|
||||||
|
|
||||||
import { BaseRoutingResolver } from '../base/base-routing-resolver.service';
|
import { BaseRoutingResolver } from '../base/base-routing-resolver.service';
|
||||||
|
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||||
|
|
||||||
const projectRoutes: Routes = [
|
const projectRoutes: Routes = [
|
||||||
{
|
{
|
||||||
@ -31,7 +32,7 @@ const projectRoutes: Routes = [
|
|||||||
path: 'projects/:id',
|
path: 'projects/:id',
|
||||||
component: ProjectDetailComponent,
|
component: ProjectDetailComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
projectResolver: BaseRoutingResolver
|
projectResolver: ProjectRoutingResolver
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{ path: 'repository', component: RepositoryComponent },
|
{ path: 'repository', component: RepositoryComponent },
|
||||||
@ -42,7 +43,12 @@ const projectRoutes: Routes = [
|
|||||||
memberResolver: BaseRoutingResolver
|
memberResolver: BaseRoutingResolver
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ path: 'log', component: AuditLogComponent }
|
{
|
||||||
|
path: 'log', component: AuditLogComponent,
|
||||||
|
resolve: {
|
||||||
|
auditLogResolver: BaseRoutingResolver
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
<h3>Projects</h3>
|
<h1>Projects</h1>
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon>New Project</button>
|
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> New Project</button>
|
||||||
<button class="btn btn-link" (click)="deleteSelectedProjects()"><clr-icon shape="close"></clr-icon>Delete</button>
|
|
||||||
<create-project (create)="createProject($event)" (openModal)="openModal($event)"></create-project>
|
<create-project (create)="createProject($event)" (openModal)="openModal($event)"></create-project>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-5">
|
||||||
<filter-project (filter)="filterProjects($event)"></filter-project>
|
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||||
<search-project (search)="searchProjects($event)"></search-project>
|
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||||
|
{{projectTypes[currentFilteredType]}}
|
||||||
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(0)">{{projectTypes[0]}}</a>
|
||||||
|
<a href="javascript:void(0)" clrDropdownItem (click)="doFilterProjects(1)">{{projectTypes[1]}}</a>
|
||||||
|
</div>
|
||||||
|
</clr-dropdown>
|
||||||
|
<clr-icon shape="filter" style="position: relative; left: 15px;"></clr-icon><input style="padding-left: 20px;" type="text" placeholder="Search for projects" #searchProject (keyup.enter)="doSearchProjects(searchProject.value)" >
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<list-project (actionPerform)="actionPerform($event)"></list-project>
|
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)"></list-project>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -2,9 +2,17 @@ import { Component, OnInit, ViewChild } from '@angular/core';
|
|||||||
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { ListProjectComponent } from './list-project/list-project.component';
|
import { Project } from './project';
|
||||||
|
import { ProjectService } from './project.service';
|
||||||
|
|
||||||
import { CreateProjectComponent } from './create-project/create-project.component';
|
import { CreateProjectComponent } from './create-project/create-project.component';
|
||||||
|
|
||||||
|
import { ListProjectComponent } from './list-project/list-project.component';
|
||||||
|
|
||||||
|
import { MessageService } from '../global-message/message.service';
|
||||||
|
|
||||||
|
export const types: {} = { 0: 'My Projects', 1: 'Public Projects'};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'project',
|
selector: 'project',
|
||||||
templateUrl: 'project.component.html',
|
templateUrl: 'project.component.html',
|
||||||
@ -12,45 +20,74 @@ import { CreateProjectComponent } from './create-project/create-project.componen
|
|||||||
})
|
})
|
||||||
export class ProjectComponent implements OnInit {
|
export class ProjectComponent implements OnInit {
|
||||||
|
|
||||||
@ViewChild(ListProjectComponent)
|
selected = [];
|
||||||
listProjects: ListProjectComponent;
|
changedProjects: Project[];
|
||||||
|
projectTypes = types;
|
||||||
|
|
||||||
@ViewChild(CreateProjectComponent)
|
@ViewChild(CreateProjectComponent)
|
||||||
creationProject: CreateProjectComponent;
|
creationProject: CreateProjectComponent;
|
||||||
|
|
||||||
|
@ViewChild(ListProjectComponent)
|
||||||
|
listProject: ListProjectComponent;
|
||||||
|
|
||||||
|
currentFilteredType: number = 0;
|
||||||
lastFilteredType: number = 0;
|
lastFilteredType: number = 0;
|
||||||
|
|
||||||
|
constructor(private projectService: ProjectService, private messageService: MessageService){}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.retrieve('', this.lastFilteredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieve(name: string, isPublic: number): void {
|
||||||
|
this.projectService
|
||||||
|
.listProjects(name, isPublic)
|
||||||
|
.subscribe(
|
||||||
|
response => this.changedProjects = response,
|
||||||
|
error => this.messageService.announceMessage(error));
|
||||||
|
}
|
||||||
|
|
||||||
openModal(): void {
|
openModal(): void {
|
||||||
this.creationProject.newProject();
|
this.creationProject.newProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSelectedProjects(): void {
|
createProject(created: boolean) {
|
||||||
this.listProjects.deleteSelectedProjects();
|
if(created) {
|
||||||
|
this.retrieve('', this.lastFilteredType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createProject(created: boolean): void {
|
doSearchProjects(projectName: string): void {
|
||||||
console.log('Project has been created:' + created);
|
|
||||||
this.listProjects.retrieve('', 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterProjects(type: number): void {
|
|
||||||
this.lastFilteredType = type;
|
|
||||||
this.listProjects.retrieve('', type);
|
|
||||||
console.log('Projects were filtered by:' + type);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
searchProjects(projectName: string): void {
|
|
||||||
console.log('Search for project name:' + projectName);
|
console.log('Search for project name:' + projectName);
|
||||||
this.listProjects.retrieve(projectName, this.lastFilteredType);
|
this.retrieve(projectName, this.lastFilteredType);
|
||||||
}
|
}
|
||||||
|
|
||||||
actionPerform(performed: boolean): void {
|
doFilterProjects(filteredType: number): void {
|
||||||
this.listProjects.retrieve('', 0);
|
console.log('Filter projects with type:' + types[filteredType]);
|
||||||
|
this.lastFilteredType = filteredType;
|
||||||
|
this.currentFilteredType = filteredType;
|
||||||
|
this.retrieve('', this.lastFilteredType);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
toggleProject(p: Project) {
|
||||||
this.listProjects.retrieve('', 0);
|
this.projectService
|
||||||
|
.toggleProjectPublic(p.project_id, p.public)
|
||||||
|
.subscribe(
|
||||||
|
response=>console.log('Successful toggled project_id:' + p.project_id),
|
||||||
|
error=>this.messageService.announceMessage(error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProject(p: Project) {
|
||||||
|
this.projectService
|
||||||
|
.deleteProject(p.project_id)
|
||||||
|
.subscribe(
|
||||||
|
response=>{
|
||||||
|
console.log('Successful delete project_id:' + p.project_id);
|
||||||
|
this.retrieve('', this.lastFilteredType);
|
||||||
|
},
|
||||||
|
error=>console.log(error)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,18 +7,19 @@ import { LogModule } from '../log/log.module';
|
|||||||
|
|
||||||
import { ProjectComponent } from './project.component';
|
import { ProjectComponent } from './project.component';
|
||||||
import { CreateProjectComponent } from './create-project/create-project.component';
|
import { CreateProjectComponent } from './create-project/create-project.component';
|
||||||
import { SearchProjectComponent } from './search-project/search-project.component';
|
|
||||||
import { FilterProjectComponent } from './filter-project/filter-project.component';
|
|
||||||
import { ActionProjectComponent } from './action-project/action-project.component';
|
import { ActionProjectComponent } from './action-project/action-project.component';
|
||||||
import { ListProjectComponent } from './list-project/list-project.component';
|
import { ListProjectComponent } from './list-project/list-project.component';
|
||||||
|
|
||||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||||
|
|
||||||
import { MemberComponent } from './member/member.component';
|
import { MemberComponent } from './member/member.component';
|
||||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||||
|
|
||||||
import { ProjectRoutingModule } from './project-routing.module';
|
import { ProjectRoutingModule } from './project-routing.module';
|
||||||
|
|
||||||
import { ProjectService } from './project.service';
|
import { ProjectService } from './project.service';
|
||||||
import { MemberService } from './member/member.service';
|
import { MemberService } from './member/member.service';
|
||||||
|
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -31,16 +32,14 @@ import { MemberService } from './member/member.service';
|
|||||||
declarations: [
|
declarations: [
|
||||||
ProjectComponent,
|
ProjectComponent,
|
||||||
CreateProjectComponent,
|
CreateProjectComponent,
|
||||||
SearchProjectComponent,
|
|
||||||
FilterProjectComponent,
|
|
||||||
ActionProjectComponent,
|
ActionProjectComponent,
|
||||||
ListProjectComponent,
|
ListProjectComponent,
|
||||||
ProjectDetailComponent,
|
ProjectDetailComponent,
|
||||||
MemberComponent,
|
MemberComponent,
|
||||||
AddMemberComponent
|
AddMemberComponent
|
||||||
],
|
],
|
||||||
exports: [ ListProjectComponent ],
|
exports: [ ProjectComponent ],
|
||||||
providers: [ ProjectService, MemberService ]
|
providers: [ ProjectRoutingResolver, ProjectService, MemberService ]
|
||||||
})
|
})
|
||||||
export class ProjectModule {
|
export class ProjectModule {
|
||||||
|
|
||||||
|
@ -22,6 +22,14 @@ export class ProjectService extends BaseService {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProject(projectId: number): Promise<Project> {
|
||||||
|
return this.http
|
||||||
|
.get(url_prefix + `/api/projects/${projectId}`)
|
||||||
|
.toPromise()
|
||||||
|
.then(response=>response.json() as Project)
|
||||||
|
.catch(error=>Observable.throw(error));
|
||||||
|
}
|
||||||
|
|
||||||
listProjects(name: string, isPublic: number): Observable<Project[]>{
|
listProjects(name: string, isPublic: number): Observable<Project[]>{
|
||||||
return this.http
|
return this.http
|
||||||
.get(url_prefix + `/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
.get(url_prefix + `/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
||||||
|
@ -29,5 +29,4 @@ export class Project {
|
|||||||
update_time: Date;
|
update_time: Date;
|
||||||
current_user_role_id: number;
|
current_user_role_id: number;
|
||||||
repo_count: number;
|
repo_count: number;
|
||||||
selected: boolean;
|
|
||||||
}
|
}
|
@ -1 +0,0 @@
|
|||||||
<input type="text" placeholder="Search for projects" #searchProject (keyup.enter)="doSearch(searchProject.value)" >
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'search-project',
|
|
||||||
templateUrl: 'search-project.component.html'
|
|
||||||
})
|
|
||||||
export class SearchProjectComponent {
|
|
||||||
@Output() search = new EventEmitter<string>();
|
|
||||||
|
|
||||||
doSearch(projectName) {
|
|
||||||
this.search.emit(projectName);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
.filter-icon {
|
||||||
|
position: relative;
|
||||||
|
right: -12px;
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<span>
|
||||||
|
<clr-icon shape="filter" size="12" class="is-solid filter-icon"></clr-icon>
|
||||||
|
<input type="text" style="padding-left: 15px;" (keyup)="valueChange()" placeholder="{{placeHolder}}" [(ngModel)]="currentValue"/>
|
||||||
|
</span>
|
43
src/clarity-seed/src/app/shared/filter/filter.component.ts
Normal file
43
src/clarity-seed/src/app/shared/filter/filter.component.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
import 'rxjs/add/operator/debounceTime';
|
||||||
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'grid-filter',
|
||||||
|
templateUrl: 'filter.component.html',
|
||||||
|
styleUrls: ['filter.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class FilterComponent implements OnInit{
|
||||||
|
private placeHolder: string = "";
|
||||||
|
private currentValue: string = "";
|
||||||
|
private leadingSpacesAdded: boolean = false;
|
||||||
|
private filerAction: Function;
|
||||||
|
|
||||||
|
private filterTerms = new Subject<string>();
|
||||||
|
|
||||||
|
@Output("filter") private filterEvt = new EventEmitter<string>();
|
||||||
|
|
||||||
|
@Input("filterPlaceholder")
|
||||||
|
public set flPlaceholder(placeHolder: string) {
|
||||||
|
this.placeHolder = placeHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.filterTerms
|
||||||
|
.debounceTime(300)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe(terms => {
|
||||||
|
this.filterEvt.emit(terms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
valueChange(): void {
|
||||||
|
//Send out filter terms
|
||||||
|
this.filterTerms.next(this.currentValue.trim());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<span style="float: right; margin-right: 24px;">
|
||||||
|
<clr-dropdown [clrMenuPosition]="'bottom-right'" [clrCloseMenuOnItemClick]="true" style="position: absolute;">
|
||||||
|
<button clrDropdownToggle>
|
||||||
|
<clr-icon shape="ellipses-vertical"></clr-icon>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</clr-dropdown>
|
||||||
|
</span>
|
@ -0,0 +1,9 @@
|
|||||||
|
import {Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "harbor-action-overflow",
|
||||||
|
templateUrl: "harbor-action-overflow.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
export class HarborActionOverflow {
|
||||||
|
}
|
@ -7,7 +7,7 @@ export function maxLengthExtValidator(length: number): ValidatorFn {
|
|||||||
return (control: AbstractControl): { [key: string]: any } => {
|
return (control: AbstractControl): { [key: string]: any } => {
|
||||||
const value: string = control.value
|
const value: string = control.value
|
||||||
if (!value || value.trim() === "") {
|
if (!value || value.trim() === "") {
|
||||||
return { 'maxLengthExt': 0 };
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regExp = new RegExp(assiiChars, 'i');
|
const regExp = new RegExp(assiiChars, 'i');
|
||||||
@ -16,7 +16,7 @@ export function maxLengthExtValidator(length: number): ValidatorFn {
|
|||||||
|
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
if (regExp.test(value[i])) {
|
if (regExp.test(value[i])) {
|
||||||
count += 2;
|
count += 3;
|
||||||
} else {
|
} else {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,6 @@ export class MaxLengthExtValidatorDirective implements Validator, OnChanges {
|
|||||||
} else {
|
} else {
|
||||||
this.valFn = Validators.nullValidator;
|
this.valFn = Validators.nullValidator;
|
||||||
}
|
}
|
||||||
console.info(changes, this.maxLengthExt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(control: AbstractControl): { [key: string]: any } {
|
validate(control: AbstractControl): { [key: string]: any } {
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CoreModule } from '../core/core.module';
|
import { CoreModule } from '../core/core.module';
|
||||||
import { AccountModule } from '../account/account.module';
|
//import { AccountModule } from '../account/account.module';
|
||||||
|
|
||||||
import { SessionService } from '../shared/session.service';
|
import { SessionService } from '../shared/session.service';
|
||||||
import { MessageComponent } from '../global-message/message.component';
|
import { MessageComponent } from '../global-message/message.component';
|
||||||
import { MessageService } from '../global-message/message.service';
|
import { MessageService } from '../global-message/message.service';
|
||||||
import { MaxLengthExtValidatorDirective } from './max-length-ext.directive';
|
import { MaxLengthExtValidatorDirective } from './max-length-ext.directive';
|
||||||
|
import { FilterComponent } from './filter/filter.component';
|
||||||
|
import { HarborActionOverflow } from './harbor-action-overflow/harbor-action-overflow';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CoreModule,
|
CoreModule
|
||||||
AccountModule
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MessageComponent,
|
MessageComponent,
|
||||||
MaxLengthExtValidatorDirective
|
MaxLengthExtValidatorDirective,
|
||||||
|
FilterComponent,
|
||||||
|
HarborActionOverflow
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
AccountModule,
|
|
||||||
MessageComponent,
|
MessageComponent,
|
||||||
MaxLengthExtValidatorDirective
|
MaxLengthExtValidatorDirective,
|
||||||
|
FilterComponent,
|
||||||
|
HarborActionOverflow
|
||||||
],
|
],
|
||||||
providers: [SessionService, MessageService]
|
providers: [SessionService, MessageService]
|
||||||
})
|
})
|
||||||
|
72
src/clarity-seed/src/app/user/new-user-form.component.html
Normal file
72
src/clarity-seed/src/app/user/new-user-form.component.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<div>
|
||||||
|
<form #newUserFrom="ngForm" class="form">
|
||||||
|
<section class="form-block">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="col-md-4 required">Username</label>
|
||||||
|
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-right" [class.invalid]="usernameInput.invalid && (usernameInput.dirty || usernameInput.touched)">
|
||||||
|
<input type="text" required pattern='[^"~#$%]+' maxLengthExt="20" #usernameInput="ngModel" name="username" [(ngModel)]="newUser.username" id="username" size="46">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Username is required and should match the following rules:Not contain '"~#$%"' and length less than 20
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email" class="col-md-4 required">Email</label>
|
||||||
|
<label for="email" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="eamilInput.invalid && (eamilInput.dirty || eamilInput.touched)">
|
||||||
|
<input name="email" type="text" #eamilInput="ngModel" [(ngModel)]="newUser.email"
|
||||||
|
required
|
||||||
|
pattern='^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$' id="email" size="46">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Email should be a valid email address like name@example.com
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="realname" class="col-md-4 required">Full name</label>
|
||||||
|
<label for="realname" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="fullNameInput.invalid && (fullNameInput.dirty || fullNameInput.touched)">
|
||||||
|
<input type="text" name="realname" #fullNameInput="ngModel" [(ngModel)]="newUser.realname" required maxLengthExt="20" id="realname" size="46">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Max length of full name is 20
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPassword" class="required">New Password</label>
|
||||||
|
<label for="newPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [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)]="newUser.password"
|
||||||
|
#newPassInput="ngModel" size="46">
|
||||||
|
<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="confirmPassword" class="required">Confirm Password</label>
|
||||||
|
<label for="confirmPassword" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="(confirmPassInput.invalid && (confirmPassInput.dirty || confirmPassInput.touched)) || (!confirmPassInput.invalid && confirmPassInput.value != newPassInput.value)">
|
||||||
|
<input type="password" id="confirmPassword" placeholder="Confirm new password"
|
||||||
|
required
|
||||||
|
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{7,}$"
|
||||||
|
name="confirmPassword"
|
||||||
|
[(ngModel)]="confirmedPwd"
|
||||||
|
#confirmPassInput="ngModel" size="46">
|
||||||
|
<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>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="comment" class="col-md-4">Comments</label>
|
||||||
|
<label for="comment" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-right" [class.invalid]="commentInput.invalid && (commentInput.dirty || commentInput.touched)">
|
||||||
|
<input type="text" #commentInput="ngModel" name="comment" [(ngModel)]="newUser.comment" maxLengthExt="20" id="comment" size="46">
|
||||||
|
<span class="tooltip-content">
|
||||||
|
Length of comment should be less than 20
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</div>
|
52
src/clarity-seed/src/app/user/new-user-form.component.ts
Normal file
52
src/clarity-seed/src/app/user/new-user-form.component.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Component, ViewChild, AfterViewChecked, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'new-user-form',
|
||||||
|
templateUrl: 'new-user-form.component.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class NewUserFormComponent implements AfterViewChecked {
|
||||||
|
newUser: User = new User();
|
||||||
|
confirmedPwd: string = "";
|
||||||
|
|
||||||
|
newUserFormRef: NgForm;
|
||||||
|
@ViewChild("newUserFrom") newUserForm: NgForm;
|
||||||
|
|
||||||
|
//Notify the form value changes
|
||||||
|
@Output() valueChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
let pwdEqualStatus = true;
|
||||||
|
if(this.newUserForm.controls["confirmPassword"] &&
|
||||||
|
this.newUserForm.controls["newPassword"]){
|
||||||
|
pwdEqualStatus = this.newUserForm.controls["confirmPassword"].value === this.newUserForm.controls["newPassword"].value;
|
||||||
|
}
|
||||||
|
return this.newUserForm &&
|
||||||
|
this.newUserForm.valid && pwdEqualStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewChecked(): void {
|
||||||
|
if (this.newUserFormRef != this.newUserForm) {
|
||||||
|
this.newUserFormRef = this.newUserForm;
|
||||||
|
if (this.newUserFormRef) {
|
||||||
|
this.newUserFormRef.valueChanges.subscribe(data => {
|
||||||
|
this.valueChange.emit(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return the current user data
|
||||||
|
getData(): User {
|
||||||
|
return this.newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
if(this.newUserForm){
|
||||||
|
this.newUserForm.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/clarity-seed/src/app/user/new-user-modal.component.html
Normal file
19
src/clarity-seed/src/app/user/new-user-modal.component.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalSize]="'lg'">
|
||||||
|
<h3 class="modal-title">Add User</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<new-user-form (valueChange)="formValueChange($event)"></new-user-form>
|
||||||
|
<div style="height: 30px;"></div>
|
||||||
|
<clr-alert [clrAlertType]="'alert-danger'" [clrAlertClosable]="true" [(clrAlertClosed)]="alertClose">
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">
|
||||||
|
{{errorMessage}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</clr-alert>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="spinner spinner-inline" style="top:8px;" [hidden]="inProgress === false"> </span>
|
||||||
|
<button type="button" class="btn btn-outline" (click)="close()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" [disabled]="!isValid || inProgress" (click)="create()">Ok</button>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
106
src/clarity-seed/src/app/user/new-user-modal.component.ts
Normal file
106
src/clarity-seed/src/app/user/new-user-modal.component.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { Component, ViewChild, Output,EventEmitter } from '@angular/core';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
|
import { NewUserFormComponent } from './new-user-form.component';
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
|
import { SessionService } from '../shared/session.service';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "new-user-modal",
|
||||||
|
templateUrl: "new-user-modal.component.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
export class NewUserModalComponent {
|
||||||
|
opened: boolean = false;
|
||||||
|
alertClose: boolean = true;
|
||||||
|
private error: any;
|
||||||
|
private onGoing: boolean = false;
|
||||||
|
|
||||||
|
@Output() addNew = new EventEmitter<User>();
|
||||||
|
|
||||||
|
constructor(private session: SessionService,
|
||||||
|
private userService: UserService) { }
|
||||||
|
|
||||||
|
@ViewChild(NewUserFormComponent)
|
||||||
|
private newUserForm: NewUserFormComponent;
|
||||||
|
|
||||||
|
private getNewUser(): User {
|
||||||
|
return this.newUserForm.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get inProgress(): boolean {
|
||||||
|
return this.onGoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
return this.newUserForm.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get errorMessage(): string {
|
||||||
|
if (this.error) {
|
||||||
|
if (this.error.message) {
|
||||||
|
return this.error.message;
|
||||||
|
} else {
|
||||||
|
if (this.error._body) {
|
||||||
|
return this.error._body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
formValueChange(flag: boolean): void {
|
||||||
|
if (!this.alertClose) {
|
||||||
|
this.alertClose = true;//If alert is shown, then close it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open(): void {
|
||||||
|
this.opened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.newUserForm.reset();//Reset form
|
||||||
|
this.opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create new user
|
||||||
|
create(): void {
|
||||||
|
//Double confirm everything is ok
|
||||||
|
//Form is valid
|
||||||
|
if(!this.isValid){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//We have new user data
|
||||||
|
let u = this.getNewUser();
|
||||||
|
if(!u){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Session is ok and role is matched
|
||||||
|
let account = this.session.getCurrentUser();
|
||||||
|
if(!account || account.has_admin_role === 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start process
|
||||||
|
this.onGoing = true;
|
||||||
|
|
||||||
|
this.userService.addUser(u)
|
||||||
|
.then(() => {
|
||||||
|
this.onGoing = false;
|
||||||
|
//TODO:
|
||||||
|
//As no response data returned, can not add it to list directly
|
||||||
|
|
||||||
|
this.addNew.emit(u);
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.onGoing = false;
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
32
src/clarity-seed/src/app/user/user.component.css
Normal file
32
src/clarity-seed/src/app/user/user.component.css
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.custom-h2 {
|
||||||
|
margin-top: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-add-button {
|
||||||
|
font-size: medium;
|
||||||
|
margin-left: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-icon {
|
||||||
|
position: relative;
|
||||||
|
right: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-pos {
|
||||||
|
float: right;
|
||||||
|
margin-right: 24px;
|
||||||
|
position: relative;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-panel-pos {
|
||||||
|
position: relative;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: -4px;
|
||||||
|
top: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<div>
|
||||||
|
<h2 class="custom-h2">Users</h2>
|
||||||
|
<div class="action-panel-pos">
|
||||||
|
<span>
|
||||||
|
<clr-icon shape="plus" class="is-highlight" size="24"></clr-icon>
|
||||||
|
<button type="submit" class="btn btn-link custom-add-button" (click)="addNewUser()">USER</button>
|
||||||
|
</span>
|
||||||
|
<grid-filter class="filter-pos" filterPlaceholder="Filter users" (filter)="doFilter($event)"></grid-filter>
|
||||||
|
<span class="refresh-btn" (click)="refreshUser()">
|
||||||
|
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
||||||
|
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Administrator</clr-dg-column>
|
||||||
|
<clr-dg-column>Email</clr-dg-column>
|
||||||
|
<clr-dg-column>Registration time</clr-dg-column>
|
||||||
|
<clr-dg-row *clrDgItems="let user of users" [clrDgItem]="user">
|
||||||
|
<clr-dg-cell>{{user.username}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{user.has_admin_role?"Yes":"No"}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{user.email}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
{{user.creation_time}}
|
||||||
|
<harbor-action-overflow>
|
||||||
|
<a href="javascript:void(0)" class="dropdown-item" (click)="changeAdminRole(user)">{{user.has_admin_role?"Disable administrator":"Enable administrator"}}</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="javascript:void(0)" class="dropdown-item" (click)="deleteUser(user.user_id)">Delete</a>
|
||||||
|
</harbor-action-overflow>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer>{{users.length}} users</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
<new-user-modal (addNew)="addUserToList($event)"></new-user-modal>
|
||||||
|
</div>
|
@ -1,7 +1,121 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { User } from './user';
|
||||||
|
import { NewUserModalComponent } from './new-user-modal.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'harbor-user',
|
selector: 'harbor-user',
|
||||||
templateUrl: 'user.component.html'
|
templateUrl: 'user.component.html',
|
||||||
|
styleUrls: ['user.component.css'],
|
||||||
|
|
||||||
|
providers: [UserService]
|
||||||
})
|
})
|
||||||
export class UserComponent {}
|
|
||||||
|
export class UserComponent implements OnInit {
|
||||||
|
users: User[] = [];
|
||||||
|
originalUsers: Promise<User[]>;
|
||||||
|
private onGoing: boolean = false;
|
||||||
|
|
||||||
|
@ViewChild(NewUserModalComponent)
|
||||||
|
private newUserDialog: NewUserModalComponent;
|
||||||
|
|
||||||
|
constructor(private userService: UserService) { }
|
||||||
|
|
||||||
|
private isMatchFilterTerm(terms: string, testedItem: string): boolean {
|
||||||
|
return testedItem.indexOf(terms) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get inProgress(): boolean {
|
||||||
|
return this.onGoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.refreshUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Filter items by keywords
|
||||||
|
doFilter(terms: string): void {
|
||||||
|
this.originalUsers.then(users => {
|
||||||
|
if (terms.trim() === "") {
|
||||||
|
this.users = users;
|
||||||
|
} else {
|
||||||
|
this.users = users.filter(user => {
|
||||||
|
return this.isMatchFilterTerm(terms, user.username);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Disable the admin role for the specified user
|
||||||
|
changeAdminRole(user: User): void {
|
||||||
|
//Double confirm user is existing
|
||||||
|
if (!user || user.user_id === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Value copy
|
||||||
|
let updatedUser: User = Object.assign({}, user);
|
||||||
|
|
||||||
|
if (updatedUser.has_admin_role === 0) {
|
||||||
|
updatedUser.has_admin_role = 1;//Set as admin
|
||||||
|
} else {
|
||||||
|
updatedUser.has_admin_role = 0;//Set as none admin
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userService.updateUser(updatedUser)
|
||||||
|
.then(() => {
|
||||||
|
//Change view now
|
||||||
|
user.has_admin_role = updatedUser.has_admin_role;
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error))//TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the specified user
|
||||||
|
deleteUser(userId: number): void {
|
||||||
|
if (userId === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userService.deleteUser(userId)
|
||||||
|
.then(() => {
|
||||||
|
//Remove it from current user list
|
||||||
|
//and then view refreshed
|
||||||
|
this.originalUsers.then(users => {
|
||||||
|
this.users = users.filter(user => user.user_id != userId);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));//TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
//Refresh the user list
|
||||||
|
refreshUser(): void {
|
||||||
|
//Start to get
|
||||||
|
this.onGoing = true;
|
||||||
|
|
||||||
|
this.originalUsers = this.userService.getUsers()
|
||||||
|
.then(users => {
|
||||||
|
this.onGoing = false;
|
||||||
|
|
||||||
|
this.users = users;
|
||||||
|
return users;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.onGoing = false;
|
||||||
|
console.error(error);//TODO:
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add new user
|
||||||
|
addNewUser(): void {
|
||||||
|
this.newUserDialog.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add user to the user list
|
||||||
|
addUserToList(user: User): void {
|
||||||
|
//Currently we can only add it by reloading all
|
||||||
|
this.refreshUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { UserComponent } from './user.component';
|
import { UserComponent } from './user.component';
|
||||||
|
import { NewUserFormComponent } from './new-user-form.component';
|
||||||
|
import { NewUserModalComponent } from './new-user-modal.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
UserComponent
|
UserComponent,
|
||||||
|
NewUserFormComponent,
|
||||||
|
NewUserModalComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
UserComponent
|
UserComponent,
|
||||||
|
NewUserFormComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class UserModule {
|
export class UserModule {
|
||||||
|
59
src/clarity-seed/src/app/user/user.service.ts
Normal file
59
src/clarity-seed/src/app/user/user.service.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||||
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
|
const userMgmtEndpoint = '/api/users';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define related methods to handle account and session corresponding things
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class SessionService
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class UserService {
|
||||||
|
private httpOptions = new RequestOptions({
|
||||||
|
headers: new Headers({
|
||||||
|
"Content-Type": 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(private http: Http) { }
|
||||||
|
|
||||||
|
//Handle the related exceptions
|
||||||
|
private handleError(error: any): Promise<any> {
|
||||||
|
return Promise.reject(error.message || error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the user list
|
||||||
|
getUsers(): Promise<User[]> {
|
||||||
|
return this.http.get(userMgmtEndpoint, this.httpOptions).toPromise()
|
||||||
|
.then(response => response.json() as User[])
|
||||||
|
.catch(error => this.handleError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add new user
|
||||||
|
addUser(user: User): Promise<any> {
|
||||||
|
return this.http.post(userMgmtEndpoint, JSON.stringify(user), this.httpOptions).toPromise()
|
||||||
|
.then(() => null)
|
||||||
|
.catch(error => this.handleError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the specified user
|
||||||
|
deleteUser(userId: number): Promise<any> {
|
||||||
|
return this.http.delete(userMgmtEndpoint+"/"+userId, this.httpOptions)
|
||||||
|
.toPromise()
|
||||||
|
.then(() => null)
|
||||||
|
.catch(error => this.handleError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update user to enable/disable the admin role
|
||||||
|
updateUser(user: User): Promise<any> {
|
||||||
|
return this.http.put(userMgmtEndpoint+"/"+user.user_id, JSON.stringify(user), this.httpOptions)
|
||||||
|
.toPromise()
|
||||||
|
.then(() => null)
|
||||||
|
.catch(error => this.handleError(error));
|
||||||
|
}
|
||||||
|
}
|
15
src/clarity-seed/src/app/user/user.ts
Normal file
15
src/clarity-seed/src/app/user/user.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* For user management
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class User
|
||||||
|
*/
|
||||||
|
export class User {
|
||||||
|
user_id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
password?: string;
|
||||||
|
comment?: string;
|
||||||
|
has_admin_role: number;
|
||||||
|
creation_time: string;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user