settings and users dialog
This commit is contained in:
parent
55248e25ef
commit
4876ba3c0c
@ -1 +1,9 @@
|
|||||||
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; font-src 'self'; connect-src https://definma-api.apps.de1.bosch-iot-cloud.com; form-action 'none'; frame-ancestors 'none'; base-uri 'self'";
|
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; font-src 'self'; connect-src https://definma-api.apps.de1.bosch-iot-cloud.com; form-action 'none'; frame-ancestors 'none'; base-uri 'self'";
|
||||||
|
add_header X-Frame-Options DENY
|
||||||
|
add_header X-DNS-Prefetch-Control off
|
||||||
|
add_header Strict-Transport-Security max-age=15552000
|
||||||
|
add_header X-Download-Options noopen
|
||||||
|
add_header X-Content-Type-Options nosniff
|
||||||
|
add_header X-Permitted-Cross-Domain-Policies none
|
||||||
|
add_header Referrer-Policy no-referrer
|
||||||
|
add_header X-XSS-Protection "1; mode=block"
|
||||||
|
@ -6,6 +6,8 @@ import {SampleComponent} from './sample/sample.component';
|
|||||||
import {SamplesComponent} from './samples/samples.component';
|
import {SamplesComponent} from './samples/samples.component';
|
||||||
import {DocumentationComponent} from './documentation/documentation.component';
|
import {DocumentationComponent} from './documentation/documentation.component';
|
||||||
import {TemplatesComponent} from './templates/templates.component';
|
import {TemplatesComponent} from './templates/templates.component';
|
||||||
|
import {SettingsComponent} from './settings/settings.component';
|
||||||
|
import {UsersComponent} from './users/users.component';
|
||||||
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@ -14,8 +16,10 @@ const routes: Routes = [
|
|||||||
{path: 'samples', component: SamplesComponent, canActivate: [LoginService]},
|
{path: 'samples', component: SamplesComponent, canActivate: [LoginService]},
|
||||||
{path: 'samples/new', component: SampleComponent, canActivate: [LoginService]},
|
{path: 'samples/new', component: SampleComponent, canActivate: [LoginService]},
|
||||||
{path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]},
|
{path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]},
|
||||||
{path: 'templates', component: TemplatesComponent}, // TODO: change after development
|
{path: 'templates', component: TemplatesComponent, canActivate: [LoginService]},
|
||||||
// {path: 'templates', component: TemplatesComponent, canActivate: [LoginService]},
|
// {path: 'users', component: UsersComponent, canActivate: [LoginService]},
|
||||||
|
{path: 'users', component: UsersComponent}, // TODO: change
|
||||||
|
{path: 'settings', component: SettingsComponent, canActivate: [LoginService]},
|
||||||
{path: 'documentation', component: DocumentationComponent},
|
{path: 'documentation', component: DocumentationComponent},
|
||||||
|
|
||||||
// if not authenticated
|
// if not authenticated
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
<nav *rbMainNavItems>
|
<nav *rbMainNavItems>
|
||||||
<a routerLink="/home" routerLinkActive="active" rbLoadingLink>Home</a>
|
<a routerLink="/home" routerLinkActive="active" rbLoadingLink>Home</a>
|
||||||
<a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLoggedIn">Samples</a>
|
<a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLoggedIn">Samples</a>
|
||||||
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isMaintain">Templates</a>
|
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="loginService.is('maintain')">Templates</a>
|
||||||
|
<a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="loginService.is('admin')">Users</a>
|
||||||
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
|
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@ -11,12 +12,9 @@
|
|||||||
<a href="javascript:" [rbPopover]="userPopover" [anchor]="popoverAnchor">
|
<a href="javascript:" [rbPopover]="userPopover" [anchor]="popoverAnchor">
|
||||||
{{loginService.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
|
{{loginService.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
|
||||||
</nav>
|
</nav>
|
||||||
<ng-template #userPopover>
|
<ng-template #userPopover let-close="close">
|
||||||
<div class="spacing">
|
<div class="spacing">
|
||||||
<p>
|
<a routerLink="/settings" (click)="close()"><span class="rb-ic rb-ic-settings"></span> Settings</a>
|
||||||
<!-- Some user specific information-->
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<button type="button" class="rb-btn rb-primary" (click)="logout()">Logout</button>
|
<button type="button" class="rb-btn rb-primary" (click)="logout()">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -3,3 +3,9 @@
|
|||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
margin-right: 40px;
|
margin-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacing {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-row-gap: 10px;
|
||||||
|
}
|
||||||
|
@ -23,6 +23,8 @@ import { ImgMagnifierComponent } from './img-magnifier/img-magnifier.component';
|
|||||||
import { ExistsPipe } from './exists.pipe';
|
import { ExistsPipe } from './exists.pipe';
|
||||||
import { TemplatesComponent } from './templates/templates.component';
|
import { TemplatesComponent } from './templates/templates.component';
|
||||||
import { ParametersPipe } from './parameters.pipe';
|
import { ParametersPipe } from './parameters.pipe';
|
||||||
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
|
import { UsersComponent } from './users/users.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -38,7 +40,9 @@ import { ParametersPipe } from './parameters.pipe';
|
|||||||
ImgMagnifierComponent,
|
ImgMagnifierComponent,
|
||||||
ExistsPipe,
|
ExistsPipe,
|
||||||
TemplatesComponent,
|
TemplatesComponent,
|
||||||
ParametersPipe
|
ParametersPipe,
|
||||||
|
SettingsComponent,
|
||||||
|
UsersComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
LocalStorageModule.forRoot({
|
LocalStorageModule.forRoot({
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
<rb-form-input name="username" label="username" appValidate="username" required [(ngModel)]="username" #usernameInput="ngModel">
|
<rb-form-input name="username" label="username" appValidate="username" required [(ngModel)]="username" #usernameInput="ngModel">
|
||||||
<ng-template rbFormValidationMessage="failure">{{usernameInput.errors.failure}}</ng-template>
|
<ng-template rbFormValidationMessage="failure">{{usernameInput.errors.failure}}</ng-template>
|
||||||
</rb-form-input>
|
</rb-form-input>
|
||||||
<rb-form-input type="password" name="password" label="password" appValidate="password" required [(ngModel)]="password" #passwordInput="ngModel">
|
<rb-form-input *ngIf="!passreset" type="password" name="password" label="password" appValidate="password" required [(ngModel)]="password" #passwordInput="ngModel">
|
||||||
<ng-template rbFormValidationMessage="failure">{{passwordInput.errors.failure}}</ng-template>
|
<ng-template rbFormValidationMessage="failure">{{passwordInput.errors.failure}}</ng-template>
|
||||||
</rb-form-input>
|
</rb-form-input>
|
||||||
<button class="rb-btn rb-primary login-button" (click)="login()" type="submit" [disabled]="!loginForm.form.valid">Login</button>
|
<rb-form-input *ngIf="passreset" type="email" name="email" label="email" email required [(ngModel)]="email" #emailInput="ngModel">
|
||||||
<span class="message">{{message}}</span>
|
<ng-template rbFormValidationMessage="failure">{{emailInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<a href="#" class="forgot-pass" (click)="passreset = !passreset">Forgot password</a>
|
||||||
|
<button class="rb-btn rb-primary login-button" (click)="login()" type="submit" [disabled]="!loginForm.form.valid">{{passreset ? 'Send' : 'Login'}}</button>
|
||||||
|
<div class="message">{{message}}</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,9 +4,14 @@
|
|||||||
|
|
||||||
.message {
|
.message {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: pre-line;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-button {
|
.login-button {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forgot-pass {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import {Component, OnInit, ViewChild} from '@angular/core';
|
|||||||
import {ValidationService} from '../services/validation.service';
|
import {ValidationService} from '../services/validation.service';
|
||||||
import {LoginService} from '../services/login.service';
|
import {LoginService} from '../services/login.service';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
|
import {ApiService} from '../services/api.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -13,13 +14,17 @@ export class LoginComponent implements OnInit {
|
|||||||
|
|
||||||
username = ''; // credentials
|
username = ''; // credentials
|
||||||
password = '';
|
password = '';
|
||||||
|
email = '';
|
||||||
message = ''; // message below login fields
|
message = ''; // message below login fields
|
||||||
|
passreset = false;
|
||||||
|
|
||||||
@ViewChild('loginForm') loginForm;
|
@ViewChild('loginForm') loginForm;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private validate: ValidationService,
|
private validate: ValidationService,
|
||||||
private loginService: LoginService,
|
private loginService: LoginService,
|
||||||
|
private api: ApiService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@ -27,6 +32,17 @@ export class LoginComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
login() {
|
login() {
|
||||||
|
if (this.passreset) {
|
||||||
|
this.api.post('/user/passreset', {name: this.username, email: this.email}, (data, err) => {
|
||||||
|
if (err) {
|
||||||
|
this.message = 'Could not find a valid user';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.message = 'Password reset, check your inbox';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
this.loginService.login(this.username, this.password).then(ok => {
|
this.loginService.login(this.username, this.password).then(ok => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.message = 'Login successful';
|
this.message = 'Login successful';
|
||||||
@ -38,3 +54,4 @@ export class LoginComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
7
src/app/models/user.model.spec.ts
Normal file
7
src/app/models/user.model.spec.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { UserModel } from './user.model';
|
||||||
|
|
||||||
|
describe('User.Model', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(new UserModel()).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
28
src/app/models/user.model.ts
Normal file
28
src/app/models/user.model.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import {BaseModel} from './base.model';
|
||||||
|
import {IdModel} from './id.model';
|
||||||
|
|
||||||
|
export class UserModel extends BaseModel{
|
||||||
|
_id: IdModel = null;
|
||||||
|
name = '';
|
||||||
|
origName = '';
|
||||||
|
email = '';
|
||||||
|
level = '';
|
||||||
|
location = '';
|
||||||
|
device_name = '';
|
||||||
|
edit = false;
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
this.origName = this.name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFormat(mode = 'user') {
|
||||||
|
const keys = ['name', 'email', 'location', 'device_name'];
|
||||||
|
if (mode === 'admin') {
|
||||||
|
keys.push('level');
|
||||||
|
}
|
||||||
|
return _.pick(this, keys);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<button class="rb-btn rb" [ngClass]="'rb-' + mode" type="button" [disabled]="disabled">
|
<button class="rb-btn rb" [ngClass]="'rb-' + mode" [type]="type" [disabled]="disabled">
|
||||||
<span class="rb-ic" [ngClass]="'rb-ic-' + icon"></span>
|
<span class="rb-ic" [ngClass]="'rb-ic-' + icon"></span>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</button>
|
</button>
|
||||||
|
@ -13,6 +13,7 @@ export class RbIconButtonComponent implements OnInit {
|
|||||||
@Input() icon: string;
|
@Input() icon: string;
|
||||||
@Input() mode: string;
|
@Input() mode: string;
|
||||||
@Input() disabled;
|
@Input() disabled;
|
||||||
|
@Input() type = 'button';
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h2>{{new ? 'Add new sample' : 'Edit sample ' + sample.number}}</h2>
|
<script src="../models/template.model.ts"></script><h2>{{new ? 'Add new sample' : 'Edit sample ' + sample.number}}</h2>
|
||||||
|
|
||||||
<rb-loading-spinner *ngIf="loading"></rb-loading-spinner>
|
<rb-loading-spinner *ngIf="loading"></rb-loading-spinner>
|
||||||
|
|
||||||
@ -6,7 +6,7 @@
|
|||||||
<!--<form #sampleForm="ngForm">-->
|
<!--<form #sampleForm="ngForm">-->
|
||||||
<div class="sample">
|
<div class="sample">
|
||||||
<div>
|
<div>
|
||||||
<rb-form-input name="materialname" label="material name" [rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" [rbDebounceTime]="0" [rbInitialOpen]="true" (keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" appValidate="stringOf" [appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true" #materialNameInput="ngModel">
|
<rb-form-input name="materialname" label="material name" [rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" [rbDebounceTime]="0" [rbInitialOpen]="true" (keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" appValidate="stringOf" [appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true" ngModel>
|
||||||
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
<ng-template rbFormValidationMessage="failure">Unknown material, add properties for new material</ng-template>
|
<ng-template rbFormValidationMessage="failure">Unknown material, add properties for new material</ng-template>
|
||||||
</rb-form-input>
|
</rb-form-input>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<h5>Sample references</h5>
|
<h5>Sample references</h5>
|
||||||
<div *ngFor="let reference of sampleReferences; index as i" class="two-col" [@inOut]>
|
<div *ngFor="let reference of sampleReferences; index as i" class="two-col" [@inOut]>
|
||||||
<div>
|
<div>
|
||||||
<rb-form-input [name]="'sr-id' + i" label="sample number" [rbFormInputAutocomplete]="sampleReferenceListBind()" [rbDebounceTime]="300" appValidate="stringOf" [appValidateArgs]="[sampleReferenceAutocomplete[i]]" (ngModelChange)="checkSampleReference($event, i)" [ngModel]="reference[0]" #idInput="ngModel">
|
<rb-form-input [name]="'sr-id' + i" label="sample number" [rbFormInputAutocomplete]="sampleReferenceListBind()" [rbDebounceTime]="300" appValidate="stringOf" [appValidateArgs]="[sampleReferenceAutocomplete[i]]" (ngModelChange)="checkSampleReference($event, i)" [ngModel]="reference[0]" ngModel>
|
||||||
<ng-template rbFormValidationMessage="failure">Unknown sample number</ng-template>
|
<ng-template rbFormValidationMessage="failure">Unknown sample number</ng-template>
|
||||||
</rb-form-input>
|
</rb-form-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,7 @@ import {Observable} from 'rxjs';
|
|||||||
|
|
||||||
// TODO: material properties, color (in material and sample (not required))
|
// TODO: material properties, color (in material and sample (not required))
|
||||||
|
|
||||||
// TODO: API $in Regex
|
// TODO: device autocomplete
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sample',
|
selector: 'app-sample',
|
||||||
|
@ -9,7 +9,17 @@ import {Observable} from 'rxjs';
|
|||||||
})
|
})
|
||||||
export class LoginService implements CanActivate {
|
export class LoginService implements CanActivate {
|
||||||
|
|
||||||
private maintainPaths = ['templates'];
|
private pathPermissions = [
|
||||||
|
{path: 'templates', permission: 'maintain'},
|
||||||
|
{path: 'users', permission: 'admin'}
|
||||||
|
];
|
||||||
|
readonly levels = [
|
||||||
|
'read',
|
||||||
|
'write',
|
||||||
|
'maintain',
|
||||||
|
'dev',
|
||||||
|
'admin'
|
||||||
|
];
|
||||||
|
|
||||||
private loggedIn;
|
private loggedIn;
|
||||||
private level;
|
private level;
|
||||||
@ -23,9 +33,28 @@ export class LoginService implements CanActivate {
|
|||||||
|
|
||||||
login(username = '', password = '') {
|
login(username = '', password = '') {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (username !== '') {
|
console.log(username);
|
||||||
|
console.log(password);
|
||||||
|
if (username !== '' || password !== '') { // some credentials given
|
||||||
|
let credentials: string[];
|
||||||
|
const credentialString: string = this.storage.get('basicAuth');
|
||||||
|
if (credentialString) { // found stored credentials
|
||||||
|
credentials = atob(credentialString).split(':');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
credentials = ['', ''];
|
||||||
|
}
|
||||||
|
if (username !== '' && password !== '') { // all credentials given
|
||||||
this.storage.set('basicAuth', btoa(username + ':' + password));
|
this.storage.set('basicAuth', btoa(username + ':' + password));
|
||||||
}
|
}
|
||||||
|
else if (username !== '') { // username given
|
||||||
|
this.storage.set('basicAuth', btoa(username + ':' + credentials[1]));
|
||||||
|
}
|
||||||
|
else if (password !== '') { // password given
|
||||||
|
this.storage.set('basicAuth', btoa(credentials[0] + ':' + password));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(this.storage.get('basicAuth'));
|
||||||
this.api.get('/authorized', (data: any, error) => {
|
this.api.get('/authorized', (data: any, error) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
if (data.status === 'Authorization successful') {
|
if (data.status === 'Authorization successful') {
|
||||||
@ -53,8 +82,8 @@ export class LoginService implements CanActivate {
|
|||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
|
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
|
||||||
return new Observable<boolean>(observer => {
|
return new Observable<boolean>(observer => {
|
||||||
const isMaintainPath = this.maintainPaths.indexOf(route.url[0].path) >= 0;
|
const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0);
|
||||||
if (!isMaintainPath || (isMaintainPath && this.isMaintain)) {
|
if (!pathPermission || this.is(pathPermission.permission)) { // check if level is permitted for path
|
||||||
if (this.loggedIn === undefined) {
|
if (this.loggedIn === undefined) {
|
||||||
this.login().then(res => {
|
this.login().then(res => {
|
||||||
observer.next(res as any);
|
observer.next(res as any);
|
||||||
@ -77,8 +106,8 @@ export class LoginService implements CanActivate {
|
|||||||
return this.loggedIn;
|
return this.loggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isMaintain() {
|
is(level) {
|
||||||
return this.level === 'maintain' || this.level === 'admin';
|
return this.levels.indexOf(this.level) >= this.levels.indexOf(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
get username() {
|
get username() {
|
||||||
|
@ -66,10 +66,16 @@ export class ValidationService {
|
|||||||
return {ok: true, error: ''};
|
return {ok: true, error: ''};
|
||||||
}
|
}
|
||||||
|
|
||||||
string(data) {
|
string(data, option = null) {
|
||||||
const {ignore, error} = Joi.string().max(128).allow('').validate(data);
|
let validator = Joi.string().max(128).allow('');
|
||||||
|
let errorMsg = 'must contain max 128 characters';
|
||||||
|
if (option === 'alphanum') {
|
||||||
|
validator = validator.alphanum();
|
||||||
|
errorMsg = 'must contain max 128 alphanumerical characters';
|
||||||
|
}
|
||||||
|
const {ignore, error} = validator.validate(data);
|
||||||
if (error) {
|
if (error) {
|
||||||
return {ok: false, error: 'must contain max 128 characters'};
|
return {ok: false, error: errorMsg};
|
||||||
}
|
}
|
||||||
return {ok: true, error: ''};
|
return {ok: true, error: ''};
|
||||||
}
|
}
|
||||||
@ -122,6 +128,13 @@ export class ValidationService {
|
|||||||
return {ok: true, error: ''};
|
return {ok: true, error: ''};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
equal(data, compare) {
|
||||||
|
if (data !== compare) {
|
||||||
|
return {ok: false, error: `must be equal`};
|
||||||
|
}
|
||||||
|
return {ok: true, error: ''};
|
||||||
|
}
|
||||||
|
|
||||||
parameterName(data) {
|
parameterName(data) {
|
||||||
const {ignore, error} = Joi.string()
|
const {ignore, error} = Joi.string()
|
||||||
.max(128)
|
.max(128)
|
||||||
|
45
src/app/settings/settings.component.html
Normal file
45
src/app/settings/settings.component.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<h2>Settings</h2>
|
||||||
|
|
||||||
|
<form #userForm="ngForm">
|
||||||
|
<rb-form-input name="name" label="user name" appValidate="username" required [(ngModel)]="user.name"
|
||||||
|
#nameInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{nameInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="email" label="email" email required [(ngModel)]="user.email" ngModel>
|
||||||
|
<ng-template rbFormValidationMessage="email">Invalid email</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="location" label="location" appValidate="string" required [appValidateArgs]="['alphanum']"
|
||||||
|
[(ngModel)]="user.location" #locationInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{locationInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="device" label="device" appValidate="string" [(ngModel)]="user.device_name"
|
||||||
|
#deviceInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-icon-button icon="save" mode="primary" type="submit" [disabled]="!userForm.form.valid" (click)="saveUser()">
|
||||||
|
Save change
|
||||||
|
</rb-icon-button>
|
||||||
|
<span class="message">{{messageUser}}</span>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h4 class="pass-heading">Change password</h4>
|
||||||
|
|
||||||
|
<form #passForm="ngForm">
|
||||||
|
<rb-form-input name="passA" type="password" label="new password" appValidate="password" required
|
||||||
|
[(ngModel)]="password" #passAInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{passAInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="passB" type="password" label="confirm password" appValidate="equal"
|
||||||
|
[appValidateArgs]="[password]" required #passBInput="ngModel" ngModel>
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{passBInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<button class="rb-btn rb-primary" type="submit" [disabled]="!passForm.form.valid" (click)="savePass()">
|
||||||
|
Change password
|
||||||
|
</button>
|
||||||
|
<span class="message">{{messagePass}}</span>
|
||||||
|
</form>
|
||||||
|
|
7
src/app/settings/settings.component.scss
Normal file
7
src/app/settings/settings.component.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.pass-heading {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
25
src/app/settings/settings.component.spec.ts
Normal file
25
src/app/settings/settings.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SettingsComponent } from './settings.component';
|
||||||
|
|
||||||
|
describe('SettingsComponent', () => {
|
||||||
|
let component: SettingsComponent;
|
||||||
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SettingsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
68
src/app/settings/settings.component.ts
Normal file
68
src/app/settings/settings.component.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {ApiService} from '../services/api.service';
|
||||||
|
import {UserModel} from '../models/user.model';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {LoginService} from '../services/login.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-settings',
|
||||||
|
templateUrl: './settings.component.html',
|
||||||
|
styleUrls: ['./settings.component.scss']
|
||||||
|
})
|
||||||
|
export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
|
user: UserModel = new UserModel();
|
||||||
|
password = '';
|
||||||
|
messageUser = '';
|
||||||
|
messagePass = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private api: ApiService,
|
||||||
|
private login: LoginService,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.api.get<UserModel>('/user', data => {
|
||||||
|
this.user.deserialize(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveUser() {
|
||||||
|
this.api.put<UserModel>('/user', this.user.sendFormat(), (data, err) => {
|
||||||
|
if (err) {
|
||||||
|
this.messageUser = err.error.status;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.login.login(data.name).then(res => {
|
||||||
|
if (res) {
|
||||||
|
this.router.navigate(['/samples']);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.messageUser = 'request not successful, try again';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
savePass() {
|
||||||
|
this.api.put<UserModel>('/user', {pass: this.password}, (ignore, err) => {
|
||||||
|
if (err) {
|
||||||
|
this.messagePass = err.error.status;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.login.login('', this.password).then(res => {
|
||||||
|
if (res) {
|
||||||
|
this.router.navigate(['/samples']);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.messagePass = 'request not successful, try again';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -38,7 +38,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
94
src/app/users/users.component.html
Normal file
94
src/app/users/users.component.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<h2>Users</h2>
|
||||||
|
|
||||||
|
<rb-icon-button icon="add" mode="primary" (click)="addNewUser()">New user</rb-icon-button>
|
||||||
|
|
||||||
|
<form *ngIf="newUser" #userForm="ngForm">
|
||||||
|
<rb-form-input name="name" label="user name" appValidate="username" required [(ngModel)]="newUser.name"
|
||||||
|
#nameInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{nameInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="email" label="email" email required [(ngModel)]="newUser.email" ngModel>
|
||||||
|
<ng-template rbFormValidationMessage="email">Invalid email</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-select name="level" label="level" required [(ngModel)]="newUser.level">
|
||||||
|
<option *ngFor="let level of login.levels" [value]="level">{{level}}</option>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-select>
|
||||||
|
<rb-form-input name="location" label="location" appValidate="string" required [appValidateArgs]="['alphanum']"
|
||||||
|
[(ngModel)]="newUser.location" #locationInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{locationInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="device" label="device" appValidate="string" [(ngModel)]="newUser.device_name"
|
||||||
|
#deviceInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="passA" type="password" label="new password" appValidate="password" required
|
||||||
|
[(ngModel)]="newUserPass" #passAInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{passAInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-form-input name="passB" type="password" label="confirm password" appValidate="equal"
|
||||||
|
[appValidateArgs]="[newUserPass]" required #passBInput="ngModel" ngModel>
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{passBInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
<rb-icon-button icon="save" mode="primary" type="submit" [disabled]="!userForm.form.valid" (click)="saveNewUser()">
|
||||||
|
Save user
|
||||||
|
</rb-icon-button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<rb-table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Device</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr *ngFor="let user of users">
|
||||||
|
<ng-container *ngIf="!user.edit; else editUser">
|
||||||
|
<td>{{user.name}}</td>
|
||||||
|
<td>{{user.email}}</td>
|
||||||
|
<td>{{user.level}}</td>
|
||||||
|
<td>{{user.location}}</td>
|
||||||
|
<td>{{user.device_name}}</td>
|
||||||
|
<td><span class="rb-ic rb-ic-edit clickable" (click)="user.edit = true"></span></td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #editUser>
|
||||||
|
<td>
|
||||||
|
<rb-form-input [name]="'name-' + user.name" appValidate="username" required [(ngModel)]="user.name" #nameInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{nameInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<rb-form-input [name]="'email-' + user.name" email required [(ngModel)]="user.email" #emailInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="email">Invalid email</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<rb-form-select [name]="'level-' + user.name" [(ngModel)]="user.level">
|
||||||
|
<option *ngFor="let level of login.levels" [value]="level">{{level}}</option>
|
||||||
|
</rb-form-select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<rb-form-input [name]="'location-' + user.name" appValidate="string" required [appValidateArgs]="['alphanum']"
|
||||||
|
[(ngModel)]="user.location" #locationInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{locationInput.errors.failure}}</ng-template>
|
||||||
|
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<rb-form-input [name]="'device-' + user.name" appValidate="string" [(ngModel)]="user.device_name"
|
||||||
|
#deviceInput="ngModel">
|
||||||
|
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
|
||||||
|
</rb-form-input>
|
||||||
|
</td>
|
||||||
|
<td><rb-icon-button icon="save" mode="primary" (click)="saveUser(user)" [disabled]="nameInput.invalid || emailInput.invalid || locationInput.invalid || deviceInput.invalid">Save</rb-icon-button></td>
|
||||||
|
</ng-template>
|
||||||
|
</tr>
|
||||||
|
</rb-table>
|
3
src/app/users/users.component.scss
Normal file
3
src/app/users/users.component.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
::ng-deep td .error-messages {
|
||||||
|
position: absolute;
|
||||||
|
}
|
25
src/app/users/users.component.spec.ts
Normal file
25
src/app/users/users.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UsersComponent } from './users.component';
|
||||||
|
|
||||||
|
describe('UsersComponent', () => {
|
||||||
|
let component: UsersComponent;
|
||||||
|
let fixture: ComponentFixture<UsersComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ UsersComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UsersComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
47
src/app/users/users.component.ts
Normal file
47
src/app/users/users.component.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {ApiService} from '../services/api.service';
|
||||||
|
import {UserModel} from '../models/user.model';
|
||||||
|
import {LoginService} from '../services/login.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-users',
|
||||||
|
templateUrl: './users.component.html',
|
||||||
|
styleUrls: ['./users.component.scss']
|
||||||
|
})
|
||||||
|
export class UsersComponent implements OnInit {
|
||||||
|
|
||||||
|
users: UserModel[] = [];
|
||||||
|
newUser: UserModel | null = null;
|
||||||
|
newUserPass = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private api: ApiService,
|
||||||
|
public login: LoginService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.api.get<UserModel[]>('/users', data => {
|
||||||
|
this.users = data.map(e => new UserModel().deserialize(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveUser(user: UserModel) {
|
||||||
|
this.api.put<UserModel>('/user/' + user.origName, user.sendFormat('admin'), data => {
|
||||||
|
user.deserialize(data);
|
||||||
|
user.edit = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNewUser() {
|
||||||
|
this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => {
|
||||||
|
this.newUser = null;
|
||||||
|
this.users.push(new UserModel().deserialize(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewUser() {
|
||||||
|
this.newUser = this.newUser ? null : new UserModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,3 +19,7 @@ button::-moz-focus-inner {
|
|||||||
.supergraphic {
|
.supergraphic {
|
||||||
background-image: url("assets/imgs/supergraphic.svg");
|
background-image: url("assets/imgs/supergraphic.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user