changed status, added restoring samples and changed password guidelines
This commit is contained in:
parent
56cd4dcbcc
commit
05a118b069
@ -1,18 +1,18 @@
|
|||||||
<rb-full-header>
|
<rb-full-header>
|
||||||
<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="login.isLoggedIn">Samples</a>
|
||||||
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLevel.dev">
|
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">
|
||||||
Templates
|
Templates
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLevel.admin">Users</a>
|
<a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.admin">Users</a>
|
||||||
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
|
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<ng-container *ngIf="loginService.isLoggedIn">
|
<ng-container *ngIf="login.isLoggedIn">
|
||||||
<nav *rbActionNavItems>
|
<nav *rbActionNavItems>
|
||||||
<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>
|
{{login.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
|
||||||
</nav>
|
</nav>
|
||||||
<ng-template #userPopover let-close="close">
|
<ng-template #userPopover let-close="close">
|
||||||
<div class="spacing">
|
<div class="spacing">
|
||||||
|
@ -21,7 +21,7 @@ export class AppComponent {
|
|||||||
bugReport = {do: '', work: ''};
|
bugReport = {do: '', work: ''};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public loginService: LoginService,
|
public login: LoginService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.loginService.logout();
|
this.login.logout();
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ export class SampleModel extends BaseModel {
|
|||||||
sample_references: {sample_id: IdModel, relation: string}[],
|
sample_references: {sample_id: IdModel, relation: string}[],
|
||||||
custom_fields: {[prop: string]: string}
|
custom_fields: {[prop: string]: string}
|
||||||
} = {comment: '', sample_references: [], custom_fields: {}};
|
} = {comment: '', sample_references: [], custom_fields: {}};
|
||||||
|
status = '';
|
||||||
added: Date = null;
|
added: Date = null;
|
||||||
|
|
||||||
deserialize(input: any): this {
|
deserialize(input: any): this {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
iconOnly></rb-icon-button>
|
iconOnly></rb-icon-button>
|
||||||
<rb-icon-button *ngIf="login.isLevel.dev" [icon]="validation ? 'checkmark' : 'clear-all'"
|
<rb-icon-button *ngIf="login.isLevel.dev" [icon]="validation ? 'checkmark' : 'clear-all'"
|
||||||
mode="secondary" (click)="validate()">
|
mode="secondary" (click)="validate()">
|
||||||
Validate
|
{{validation ? 'Validate' : 'Validation'}}
|
||||||
</rb-icon-button>
|
</rb-icon-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -19,13 +19,20 @@
|
|||||||
<div class="status-selection">
|
<div class="status-selection">
|
||||||
<label class="label">Status</label>
|
<label class="label">Status</label>
|
||||||
<rb-form-checkbox name="status-validated" [(ngModel)]="filters.status.validated"
|
<rb-form-checkbox name="status-validated" [(ngModel)]="filters.status.validated"
|
||||||
[disabled]="!filters.status.new" (ngModelChange)="loadSamples({firstPage: true})">
|
[disabled]="!filters.status.new && !filters.status.deleted"
|
||||||
|
(ngModelChange)="loadSamples({firstPage: true})">
|
||||||
validated
|
validated
|
||||||
</rb-form-checkbox>
|
</rb-form-checkbox>
|
||||||
<rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new" [disabled]="!filters.status.validated"
|
<rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new"
|
||||||
|
[disabled]="!filters.status.validated && !filters.status.deleted"
|
||||||
(ngModelChange)="loadSamples({firstPage: true})">
|
(ngModelChange)="loadSamples({firstPage: true})">
|
||||||
new
|
new
|
||||||
</rb-form-checkbox>
|
</rb-form-checkbox>
|
||||||
|
<rb-form-checkbox name="status-deleted" [(ngModel)]="filters.status.deleted"
|
||||||
|
[disabled]="!filters.status.validated && !filters.status.new"
|
||||||
|
(ngModelChange)="loadSamples({firstPage: true})" *ngIf="login.isLevel.dev">
|
||||||
|
deleted
|
||||||
|
</rb-form-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="filters.pageSize" class="selection"
|
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="filters.pageSize" class="selection"
|
||||||
(ngModelChange)="loadSamples({firstPage: true})" #pageSizeSelection>
|
(ngModelChange)="loadSamples({firstPage: true})" #pageSizeSelection>
|
||||||
@ -135,7 +142,8 @@
|
|||||||
|
|
||||||
<tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
|
<tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
|
||||||
<td *ngIf="validation">
|
<td *ngIf="validation">
|
||||||
<rb-form-checkbox [name]="'validate-' + i" (click)="stopPropagation($event)" [(ngModel)]="sample.validate">
|
<rb-form-checkbox *ngIf="sample.status !== 'deleted'" [name]="'validate-' + i" (click)="stopPropagation($event)"
|
||||||
|
[(ngModel)]="sample.validate">
|
||||||
</rb-form-checkbox>
|
</rb-form-checkbox>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="isActiveKey['number']">{{sample.number}}</td>
|
<td *ngIf="isActiveKey['number']">{{sample.number}}</td>
|
||||||
@ -151,12 +159,16 @@
|
|||||||
<td *ngIf="isActiveKey['batch']">{{sample.batch}}</td>
|
<td *ngIf="isActiveKey['batch']">{{sample.batch}}</td>
|
||||||
<td *ngIf="isActiveKey['notes']">{{sample.notes | object: ['_id', 'sample_references']}}</td>
|
<td *ngIf="isActiveKey['notes']">{{sample.notes | object: ['_id', 'sample_references']}}</td>
|
||||||
<td *ngFor="let key of activeTemplateKeys.measurements">{{sample[key[1]] | exists: key[2]}}</td>
|
<td *ngFor="let key of activeTemplateKeys.measurements">{{sample[key[1]] | exists: key[2]}}</td>
|
||||||
|
<td *ngIf="isActiveKey['status']">{{sample.status}}</td>
|
||||||
<td *ngIf="isActiveKey['added']">{{sample.added | date:'dd/MM/yy'}}</td>
|
<td *ngIf="isActiveKey['added']">{{sample.added | date:'dd/MM/yy'}}</td>
|
||||||
<td *ngIf="login.isLevel.write">
|
<td *ngIf="login.isLevel.write">
|
||||||
<a [routerLink]="'/samples/edit/' + sample._id"
|
<a [routerLink]="'/samples/edit/' + sample._id"
|
||||||
*ngIf="login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId)">
|
*ngIf="sample.status !== 'deleted' &&
|
||||||
<span class="rb-ic rb-ic-edit"></span>
|
(login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId))">
|
||||||
|
<span class="rb-ic rb-ic-edit clickable"></span>
|
||||||
</a>
|
</a>
|
||||||
|
<span class="rb-ic rb-ic-undo clickable" *ngIf="sample.status === 'deleted' && login.isLevel.dev"
|
||||||
|
(click)="restoreSample(sample._id, restoreConfirm, $event)"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</rb-table>
|
</rb-table>
|
||||||
@ -208,6 +220,13 @@
|
|||||||
<td>{{measurement.value}}</td>
|
<td>{{measurement.value}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><th>User</th><td>{{sampleDetailsSample.user}}</td></tr>
|
<tr><th>User</th><td>{{sampleDetailsSample.user}}</td></tr>
|
||||||
|
<tr><th>Status</th><td>{{sampleDetailsSample.status}}</td></tr>
|
||||||
</rb-table>
|
</rb-table>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #restoreConfirm>
|
||||||
|
<rb-dialog dialogTitle="Restore sample">
|
||||||
|
Do you really want to restore this sample?
|
||||||
|
</rb-dialog>
|
||||||
|
</ng-template>
|
||||||
|
@ -17,7 +17,7 @@ rb-table {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rb-ic.rb-ic-edit {
|
td .clickable.rb-ic {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: $color-gray-silver-sand;
|
color: $color-gray-silver-sand;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -39,13 +39,12 @@ export class SamplesComponent implements OnInit {
|
|||||||
totalSamples = 0; // total number of samples
|
totalSamples = 0; // total number of samples
|
||||||
csvUrl = ''; // store url separate so it only has to be generated when clicking the download button
|
csvUrl = ''; // store url separate so it only has to be generated when clicking the download button
|
||||||
filters = {
|
filters = {
|
||||||
status: {new: true, validated: true},
|
status: {new: true, validated: true, deleted: false},
|
||||||
pageSize: 25,
|
pageSize: 25,
|
||||||
toPage: 0,
|
toPage: 0,
|
||||||
sort: 'added-asc',
|
sort: 'added-asc',
|
||||||
filters: [
|
filters: [
|
||||||
{field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
{field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
||||||
{field: 'material.number', label: 'Material number', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
|
||||||
{field: 'material.name', label: 'Material name', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
{field: 'material.name', label: 'Material name', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
||||||
{field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
{field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
||||||
{field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
{field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']},
|
||||||
@ -72,7 +71,8 @@ export class SamplesComponent implements OnInit {
|
|||||||
{id: 'color', label: 'Color', active: true, sortable: true},
|
{id: 'color', label: 'Color', active: true, sortable: true},
|
||||||
{id: 'batch', label: 'Batch', active: true, sortable: true},
|
{id: 'batch', label: 'Batch', active: true, sortable: true},
|
||||||
{id: 'notes', label: 'Notes', active: false, sortable: false},
|
{id: 'notes', label: 'Notes', active: false, sortable: false},
|
||||||
{id: 'added', label: 'Added', active: true, sortable: true},
|
{id: 'status', label: 'Status', active: true, sortable: true},
|
||||||
|
{id: 'added', label: 'Added', active: true, sortable: true}
|
||||||
];
|
];
|
||||||
isActiveKey: {[key: string]: boolean} = {};
|
isActiveKey: {[key: string]: boolean} = {};
|
||||||
activeKeys: KeyInterface[] = [];
|
activeKeys: KeyInterface[] = [];
|
||||||
@ -97,18 +97,16 @@ export class SamplesComponent implements OnInit {
|
|||||||
this.filters.filters.find(e => e.field === 'color').autocomplete =
|
this.filters.filters.find(e => e.field === 'color').autocomplete =
|
||||||
[...new Set(this.d.arr.materials.reduce((s, e) => {s.push(...e.numbers.map(el => el.color)); return s; }, []))];
|
[...new Set(this.d.arr.materials.reduce((s, e) => {s.push(...e.numbers.map(el => el.color)); return s; }, []))];
|
||||||
this.loadSamples();
|
this.loadSamples();
|
||||||
console.log(this.d.id.materials);
|
|
||||||
});
|
});
|
||||||
this.d.load('materialSuppliers', () => {
|
this.d.load('materialSuppliers', () => {
|
||||||
this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
|
this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
|
||||||
});
|
});
|
||||||
this.d.load('materialGroups', () => {
|
this.d.load('materialGroups', () => {
|
||||||
console.log(this.d.arr.materialGroups);
|
|
||||||
this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
|
this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
|
||||||
});
|
});
|
||||||
this.d.load('userKey');
|
this.d.load('userKey');
|
||||||
this.loadTemplateKeys('material', 'type');
|
this.loadTemplateKeys('material', 'type');
|
||||||
this.loadTemplateKeys('measurement', 'added');
|
this.loadTemplateKeys('measurement', 'status');
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTemplateKeys(collection, insertBefore) {
|
loadTemplateKeys(collection, insertBefore) {
|
||||||
@ -185,9 +183,7 @@ export class SamplesComponent implements OnInit {
|
|||||||
}) { // return url to fetch samples
|
}) { // return url to fetch samples
|
||||||
const additionalTableKeys = ['material_id', '_id', 'user_id']; // keys which should always be added if export = false
|
const additionalTableKeys = ['material_id', '_id', 'user_id']; // keys which should always be added if export = false
|
||||||
const query: string[] = [];
|
const query: string[] = [];
|
||||||
query.push(
|
query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e));
|
||||||
'status=' + (this.filters.status.new && this.filters.status.validated ? 'all' : (this.filters.status.new ? 'new' : 'validated'))
|
|
||||||
);
|
|
||||||
if (options.paging) {
|
if (options.paging) {
|
||||||
if (this.samples[0]) { // do not include from-id when page size was changed
|
if (this.samples[0]) { // do not include from-id when page size was changed
|
||||||
if (!options.pagingOptions.firstPage) {
|
if (!options.pagingOptions.firstPage) {
|
||||||
@ -326,6 +322,7 @@ export class SamplesComponent implements OnInit {
|
|||||||
this.api.put('/sample/validate/' + sample._id);
|
this.api.put('/sample/validate/' + sample._id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.loadSamples();
|
||||||
this.validation = false;
|
this.validation = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -333,9 +330,25 @@ export class SamplesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreSample(id, modal, event) {
|
||||||
|
this.stopPropagation(event);
|
||||||
|
this.modalService.open(modal).then(res => {
|
||||||
|
if (res) {
|
||||||
|
this.api.put('/sample/restore/' + id, {}, ignore => {
|
||||||
|
this.samples.find(e => e._id === id).status = 'new';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
selectAll(event) {
|
selectAll(event) {
|
||||||
this.samples.forEach(sample => {
|
this.samples.forEach(sample => {
|
||||||
|
if (sample.status !== 'deleted') {
|
||||||
sample.validate = event.target.checked;
|
sample.validate = event.target.checked;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sample.validate = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export class ValidationService {
|
|||||||
.max(128);
|
.max(128);
|
||||||
|
|
||||||
private vPassword = Joi.string()
|
private vPassword = Joi.string()
|
||||||
.pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
|
.min(8)
|
||||||
.max(128);
|
.max(128);
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
@ -44,25 +44,8 @@ export class ValidationService {
|
|||||||
password(data) {
|
password(data) {
|
||||||
const {ignore, error} = this.vPassword.validate(data);
|
const {ignore, error} = this.vPassword.validate(data);
|
||||||
if (error) {
|
if (error) {
|
||||||
if (Joi.string().min(8).validate(data).error) {
|
|
||||||
return {ok: false, error: 'password must have at least 8 characters'};
|
return {ok: false, error: 'password must have at least 8 characters'};
|
||||||
}
|
}
|
||||||
else if (Joi.string().pattern(/[a-z]+/).validate(data).error) {
|
|
||||||
return {ok: false, error: 'password must have at least one lowercase character'};
|
|
||||||
}
|
|
||||||
else if (Joi.string().pattern(/[A-Z]+/).validate(data).error) {
|
|
||||||
return {ok: false, error: 'password must have at least one uppercase character'};
|
|
||||||
}
|
|
||||||
else if (Joi.string().pattern(/[0-9]+/).validate(data).error) {
|
|
||||||
return {ok: false, error: 'password must have at least one number'};
|
|
||||||
}
|
|
||||||
else if (Joi.string().pattern(/[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~]+/).validate(data).error) {
|
|
||||||
return {ok: false, error: 'password must have at least one of the following characters !"#%&\'()*+,-.\\/:;<=>?@[]^_`{|}~'};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return {ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {ok: true, error: ''};
|
return {ok: true, error: ''};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user