2
Fork 0

Properly indent all source files

This commit is contained in:
Kai S. K. Engelbart 2021-01-25 12:34:23 +01:00
parent 2d3782cc82
commit 2f6231a6b5
106 changed files with 5170 additions and 5171 deletions

View File

@ -13,43 +13,43 @@ import {DocumentationDatabaseComponent} from './documentation/documentation-data
import {PredictionComponent} from './prediction/prediction.component';
import {ModelTemplatesComponent} from './model-templates/model-templates.component';
import {DocumentationArchitectureComponent} from
'./documentation/documentation-architecture/documentation-architecture.component';
'./documentation/documentation-architecture/documentation-architecture.component';
import {MaterialsComponent} from './materials/materials.component';
import {MaterialComponent} from './material/material.component';
import {DocumentationModelsComponent} from './documentation/documentation-models/documentation-models.component';
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'home', component: HomeComponent},
{path: 'prediction', component: PredictionComponent},
{path: 'models', component: ModelTemplatesComponent},
{path: 'samples', component: SamplesComponent, canActivate: [LoginService]},
{path: 'samples/new', component: SampleComponent, canActivate: [LoginService]},
{path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]},
{path: 'materials', component: MaterialsComponent, canActivate: [LoginService]},
{path: 'materials/edit/:id', component: MaterialComponent, canActivate: [LoginService]},
{path: 'templates', component: TemplatesComponent, canActivate: [LoginService]},
{path: 'changelog', component: ChangelogComponent, canActivate: [LoginService]},
{path: 'users', component: UsersComponent, canActivate: [LoginService]},
{path: 'settings', component: SettingsComponent, canActivate: [LoginService]},
{path: 'documentation', component: DocumentationComponent},
{path: 'documentation/architecture', component: DocumentationArchitectureComponent},
{path: 'documentation/database', component: DocumentationDatabaseComponent},
{path: 'documentation/models', component: DocumentationModelsComponent},
{path: '', component: HomeComponent},
{path: 'home', component: HomeComponent},
{path: 'prediction', component: PredictionComponent},
{path: 'models', component: ModelTemplatesComponent},
{path: 'samples', component: SamplesComponent, canActivate: [LoginService]},
{path: 'samples/new', component: SampleComponent, canActivate: [LoginService]},
{path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]},
{path: 'materials', component: MaterialsComponent, canActivate: [LoginService]},
{path: 'materials/edit/:id', component: MaterialComponent, canActivate: [LoginService]},
{path: 'templates', component: TemplatesComponent, canActivate: [LoginService]},
{path: 'changelog', component: ChangelogComponent, canActivate: [LoginService]},
{path: 'users', component: UsersComponent, canActivate: [LoginService]},
{path: 'settings', component: SettingsComponent, canActivate: [LoginService]},
{path: 'documentation', component: DocumentationComponent},
{path: 'documentation/architecture', component: DocumentationArchitectureComponent},
{path: 'documentation/database', component: DocumentationDatabaseComponent},
{path: 'documentation/models', component: DocumentationModelsComponent},
// If not authenticated
{ path: '**', redirectTo: '' }
// If not authenticated
{ path: '**', redirectTo: '' }
];
const routerOptions: ExtraOptions = {
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 64],
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 64],
};
@NgModule({
imports: [RouterModule.forRoot(routes, routerOptions)],
exports: [RouterModule]
imports: [RouterModule.forRoot(routes, routerOptions)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -1,73 +1,73 @@
<rb-full-header id="top">
<nav *rbMainNavItems>
<a routerLink="/home" routerLinkActive="active" rbLoadingLink>Home</a>
<a routerLink="/prediction" routerLinkActive="active" rbLoadingLink *ngIf="login.hasPrediction">Prediction</a>
<a routerLink="/models" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Models</a>
<a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.read">Samples</a>
<a routerLink="/materials" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Materials</a>
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">
Templates
</a>
<a routerLink="/changelog" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Changelog</a>
<a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.admin">Users</a>
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
</nav>
<nav *rbMainNavItems>
<a routerLink="/home" routerLinkActive="active" rbLoadingLink>Home</a>
<a routerLink="/prediction" routerLinkActive="active" rbLoadingLink *ngIf="login.hasPrediction">Prediction</a>
<a routerLink="/models" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Models</a>
<a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.read">Samples</a>
<a routerLink="/materials" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Materials</a>
<a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">
Templates
</a>
<a routerLink="/changelog" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">Changelog</a>
<a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.admin">Users</a>
<a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
</nav>
<ng-container *ngIf="isDocumentation">
<nav *rbSubNavItems>
<a routerLink="/documentation" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">General</a>
<a routerLink="/documentation/architecture" routerLinkActive="active">Architecture</a>
<a routerLink="/documentation/database" routerLinkActive="active">Database</a>
<a routerLink="/documentation/models" routerLinkActive="active">Models</a>
</nav>
</ng-container>
<ng-container *ngIf="isDocumentation">
<nav *rbSubNavItems>
<a routerLink="/documentation" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">General</a>
<a routerLink="/documentation/architecture" routerLinkActive="active">Architecture</a>
<a routerLink="/documentation/database" routerLinkActive="active">Database</a>
<a routerLink="/documentation/models" routerLinkActive="active">Models</a>
</nav>
</ng-container>
<nav *rbMetaNavItems>
<span class="rb-ic rb-ic-question-frame clickable space-above" (click)="help()"></span>
</nav>
<nav *rbMetaNavItems>
<span class="rb-ic rb-ic-question-frame clickable space-above" (click)="help()"></span>
</nav>
<ng-container *ngIf="login.isLoggedIn">
<nav *rbActionNavItems>
<a href="javascript:" [rbPopover]="userPopover" [anchor]="popoverAnchor">
{{login.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
</nav>
<ng-template #userPopover let-close="close">
<div class="spacing">
<a routerLink="/settings" (click)="close()"><span class="rb-ic rb-ic-settings"></span>&nbsp;&nbsp;Settings</a>
<button type="button" class="rb-btn rb-primary" (click)="logout()">Logout</button>
</div>
</ng-template>
</ng-container>
<ng-container *ngIf="login.isLoggedIn">
<nav *rbActionNavItems>
<a href="javascript:" [rbPopover]="userPopover" [anchor]="popoverAnchor">
{{login.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
</nav>
<ng-template #userPopover let-close="close">
<div class="spacing">
<a routerLink="/settings" (click)="close()"><span class="rb-ic rb-ic-settings"></span>&nbsp;&nbsp;Settings</a>
<button type="button" class="rb-btn rb-primary" (click)="logout()">Logout</button>
</div>
</ng-template>
</ng-container>
<div *rbSubBrandHeader>
<rb-icon-button icon="bug" mode="secondary" class="space-right" [rbModal]="bugModal">
Bug
</rb-icon-button>
<ng-template let-close="close" #bugModal>
<h3>Report a bug</h3>
<rb-form-textarea class="bug-textarea" label="What did you do?" [(ngModel)]="bugReport.do"></rb-form-textarea>
<rb-form-textarea class="bug-textarea" label="What did not work?" [(ngModel)]="bugReport.work"></rb-form-textarea>
<a [href]="bugReportContent()">
<rb-icon-button icon="mail" mode="primary" (mouseup)="closeBugReport(close)">Send report</rb-icon-button>
</a>
</ng-template>
<span class="dev-label" *ngIf="devMode">DEVELOPMENT</span>
DeFinMa
</div>
<div *rbSubBrandHeader>
<rb-icon-button icon="bug" mode="secondary" class="space-right" [rbModal]="bugModal">
Bug
</rb-icon-button>
<ng-template let-close="close" #bugModal>
<h3>Report a bug</h3>
<rb-form-textarea class="bug-textarea" label="What did you do?" [(ngModel)]="bugReport.do"></rb-form-textarea>
<rb-form-textarea class="bug-textarea" label="What did not work?" [(ngModel)]="bugReport.work"></rb-form-textarea>
<a [href]="bugReportContent()">
<rb-icon-button icon="mail" mode="primary" (mouseup)="closeBugReport(close)">Send report</rb-icon-button>
</a>
</ng-template>
<span class="dev-label" *ngIf="devMode">DEVELOPMENT</span>
DeFinMa
</div>
</rb-full-header>
<div class="container">
<router-outlet></router-outlet>
<router-outlet></router-outlet>
</div>
<rb-icon-button icon="up" mode="primary" iconOnly (click)="toTheTop()" *ngIf="window.scrollY > 300" class="to-the-top">
</rb-icon-button>
<rb-footer-nav>
<span class="copyright">
CR/APS1 and CR/ANA1 2020
</span>
<nav>
<a [href]="'mailto:' + d.contact.mail">Contact</a>
</nav>
<span class="copyright">
CR/APS1 and CR/ANA1 2020
</span>
<nav>
<a [href]="'mailto:' + d.contact.mail">Contact</a>
</nav>
</rb-footer-nav>

View File

@ -1,26 +1,26 @@
.dev-label {
color: #F00;
font-size: 32px;
margin-right: 40px;
color: #F00;
font-size: 32px;
margin-right: 40px;
}
.spacing {
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
}
.bug-textarea {
width: 800px;
width: 800px;
}
.container {
position: relative;
overflow: hidden;
position: relative;
overflow: hidden;
}
.to-the-top {
position: fixed;
right: 1rem;
bottom: 20px;
position: fixed;
right: 1rem;
bottom: 20px;
}

View File

@ -8,76 +8,76 @@ import {DataService} from './services/data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
bugReport = {do: '', work: ''}; // Data from bug report inputs
isDocumentation = false; // True if user is on documentation pages
devMode = false;
bugReport = {do: '', work: ''}; // Data from bug report inputs
isDocumentation = false; // True if user is on documentation pages
devMode = false;
constructor(
public login: LoginService,
public router: Router,
private route: ActivatedRoute,
public window: Window,
private modal: ModalService,
public d: DataService
) {
this.devMode = isDevMode();
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.isDocumentation = /\/documentation/.test(event.url);
}
});
}
constructor(
public login: LoginService,
public router: Router,
private route: ActivatedRoute,
public window: Window,
private modal: ModalService,
public d: DataService
) {
this.devMode = isDevMode();
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.isDocumentation = /\/documentation/.test(event.url);
}
});
}
ngOnInit() {
// Try to log in user
this.login.login().then(res => {
// Return to home page if log failed, except when on documentation pages
if (!res && !(/\/documentation/.test(this.router.url))) {
this.router.navigate(['/']);
}
});
}
ngOnInit() {
// Try to log in user
this.login.login().then(res => {
// Return to home page if log failed, except when on documentation pages
if (!res && !(/\/documentation/.test(this.router.url))) {
this.router.navigate(['/']);
}
});
}
logout() {
this.login.logout();
this.router.navigate(['/']);
}
logout() {
this.login.logout();
this.router.navigate(['/']);
}
help() {
this.modal.openComponent(HelpComponent);
}
help() {
this.modal.openComponent(HelpComponent);
}
bugReportContent() {
return `mailto:${this.d.contact.mail}?subject=Bug report&body=Thanks for sending the report! Your bug will be
(hopefully) fixed soon.
%0D%0A%0D%0A--- REPORT DATA ---
%0D%0A%0D%0ATime: ${new Date().toString()}%0D%0A
URL: ${this.window.location}%0D%0A%0D%0AWhat did you do?%0D%0A${encodeURIComponent(this.bugReport.do)}
%0D%0A%0D%0AWhat did not work?%0D%0A${encodeURIComponent(this.bugReport.work)}%0D%0A%0D%0ABrowser:%0D%0A
%0D%0AappCodeName: ${navigator.appCodeName}
%0D%0AappVersion: ${navigator.appVersion}
%0D%0Alanguage: ${navigator.language}
%0D%0AonLine: ${navigator.onLine}
%0D%0Aoscpu: ${navigator.oscpu}
%0D%0Aplatform: ${navigator.platform}
%0D%0AuserAgent: ${navigator.userAgent}
%0D%0AinnerWidth: ${this.window.innerWidth}
%0D%0AinnerHeight: ${this.window.innerHeight}`;
}
bugReportContent() {
return `mailto:${this.d.contact.mail}?subject=Bug report&body=Thanks for sending the report! Your bug will be
(hopefully) fixed soon.
%0D%0A%0D%0A--- REPORT DATA ---
%0D%0A%0D%0ATime: ${new Date().toString()}%0D%0A
URL: ${this.window.location}%0D%0A%0D%0AWhat did you do?%0D%0A${encodeURIComponent(this.bugReport.do)}
%0D%0A%0D%0AWhat did not work?%0D%0A${encodeURIComponent(this.bugReport.work)}%0D%0A%0D%0ABrowser:%0D%0A
%0D%0AappCodeName: ${navigator.appCodeName}
%0D%0AappVersion: ${navigator.appVersion}
%0D%0Alanguage: ${navigator.language}
%0D%0AonLine: ${navigator.onLine}
%0D%0Aoscpu: ${navigator.oscpu}
%0D%0Aplatform: ${navigator.platform}
%0D%0AuserAgent: ${navigator.userAgent}
%0D%0AinnerWidth: ${this.window.innerWidth}
%0D%0AinnerHeight: ${this.window.innerHeight}`;
}
closeBugReport(close) {
setTimeout(() => close(), 1);
}
closeBugReport(close) {
setTimeout(() => close(), 1);
}
toTheTop() {
this.window.scroll(0, 0);
}
toTheTop() {
this.window.scroll(0, 0);
}
}

View File

@ -27,66 +27,66 @@ import { SettingsComponent } from './settings/settings.component';
import { UsersComponent } from './users/users.component';
import { ChangelogComponent } from './changelog/changelog.component';
import { DocumentationDatabaseComponent } from
'./documentation/documentation-database/documentation-database.component';
'./documentation/documentation-database/documentation-database.component';
import { PredictionComponent } from './prediction/prediction.component';
import { HelpComponent } from './help/help.component';
import { ModelTemplatesComponent } from './model-templates/model-templates.component';
import { SizePipe } from './size.pipe';
import { DocumentationArchitectureComponent } from
'./documentation/documentation-architecture/documentation-architecture.component';
'./documentation/documentation-architecture/documentation-architecture.component';
import { MaterialsComponent } from './materials/materials.component';
import { MaterialComponent } from './material/material.component';
import { DocumentationModelsComponent } from './documentation/documentation-models/documentation-models.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
HomeComponent,
SamplesComponent,
SampleComponent,
ValidateDirective,
ErrorComponent,
ObjectPipe,
DocumentationComponent,
ImgMagnifierComponent,
ExistsPipe,
TemplatesComponent,
ParametersPipe,
SettingsComponent,
UsersComponent,
ChangelogComponent,
DocumentationDatabaseComponent,
PredictionComponent,
HelpComponent,
ModelTemplatesComponent,
SizePipe,
DocumentationArchitectureComponent,
MaterialsComponent,
MaterialComponent,
DocumentationModelsComponent
],
imports: [
LocalStorageModule.forRoot({
prefix: 'definma',
storageType: 'localStorage'
}),
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
RbUiComponentsModule,
FormsModule,
HttpClientModule,
RbCustomInputsModule,
ReactiveFormsModule,
FormFieldsModule,
CommonModule,
ChartsModule
],
providers: [
ModalService,
{ provide: Window, useValue: window }
],
bootstrap: [AppComponent]
declarations: [
AppComponent,
LoginComponent,
HomeComponent,
SamplesComponent,
SampleComponent,
ValidateDirective,
ErrorComponent,
ObjectPipe,
DocumentationComponent,
ImgMagnifierComponent,
ExistsPipe,
TemplatesComponent,
ParametersPipe,
SettingsComponent,
UsersComponent,
ChangelogComponent,
DocumentationDatabaseComponent,
PredictionComponent,
HelpComponent,
ModelTemplatesComponent,
SizePipe,
DocumentationArchitectureComponent,
MaterialsComponent,
MaterialComponent,
DocumentationModelsComponent
],
imports: [
LocalStorageModule.forRoot({
prefix: 'definma',
storageType: 'localStorage'
}),
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
RbUiComponentsModule,
FormsModule,
HttpClientModule,
RbCustomInputsModule,
ReactiveFormsModule,
FormFieldsModule,
CommonModule,
ChartsModule
],
providers: [
ModalService,
{ provide: Window, useValue: window }
],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -1,46 +1,46 @@
<div class="header">
<rb-form-date-input name="dateInput" label="older than" [options]="{enableTime: true}"
[(ngModel)]="timestamp" (ngModelChange)="loadChangelog()">
</rb-form-date-input>
<rb-form-date-input name="dateInput" label="older than" [options]="{enableTime: true}"
[(ngModel)]="timestamp" (ngModelChange)="loadChangelog()">
</rb-form-date-input>
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="pageSize" (ngModelChange)="loadChangelog()">
<option value="3">3</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
</rb-form-select>
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="pageSize" (ngModelChange)="loadChangelog()">
<option value="3">3</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
</rb-form-select>
<button class="rb-btn rb-link" type="button" (click)="loadChangelog(1)">
next page
<span class="rb-ic rb-ic-forward-right"></span>
</button>
<button class="rb-btn rb-link" type="button" (click)="loadChangelog(1)">
next page
<span class="rb-ic rb-ic-forward-right"></span>
</button>
</div>
<rb-table ellipsis>
<tr>
<th>Date</th>
<th>Action</th>
<th>Data</th>
</tr>
<tr *ngFor="let entry of changelog; index as i" class="clickable" (click)="showDetails(i, sampleModal)">
<td>{{entry.date}}</td>
<td>{{entry.action}}</td>
<td>{{entry.data | json}}</td>
</tr>
<tr>
<th>Date</th>
<th>Action</th>
<th>Data</th>
</tr>
<tr *ngFor="let entry of changelog; index as i" class="clickable" (click)="showDetails(i, sampleModal)">
<td>{{entry.date}}</td>
<td>{{entry.action}}</td>
<td>{{entry.data | json}}</td>
</tr>
</rb-table>
<ng-template #sampleModal>
<h4>Date</h4>
<p class="space-below">{{changelog[modalDetail].date}}</p>
<h4>Action</h4>
<p class="space-below">{{changelog[modalDetail].action}}</p>
<h4>Collection</h4>
<p class="space-below">{{changelog[modalDetail].collection}}</p>
<h4>Conditions</h4>
<p class="space-below">{{changelog[modalDetail].conditions | json}}</p>
<h4>Data</h4>
<p class="space-below">{{changelog[modalDetail].data | json}}</p>
<h4>Date</h4>
<p class="space-below">{{changelog[modalDetail].date}}</p>
<h4>Action</h4>
<p class="space-below">{{changelog[modalDetail].action}}</p>
<h4>Collection</h4>
<p class="space-below">{{changelog[modalDetail].collection}}</p>
<h4>Conditions</h4>
<p class="space-below">{{changelog[modalDetail].conditions | json}}</p>
<h4>Data</h4>
<p class="space-below">{{changelog[modalDetail].data | json}}</p>
</ng-template>

View File

@ -2,20 +2,20 @@
.header {
& > * {
float: left;
}
& > * {
float: left;
}
button {
float: right;
}
button {
float: right;
}
}
tr.clickable {
background: none;
transition: background-color 0.5s;
background: none;
transition: background-color 0.5s;
&:hover {
background: $color-gray-mercury;
}
&:hover {
background: $color-gray-mercury;
}
}

View File

@ -4,44 +4,44 @@ import {ApiService} from '../services/api.service';
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
@Component({
selector: 'app-changelog',
templateUrl: './changelog.component.html',
styleUrls: ['./changelog.component.scss']
selector: 'app-changelog',
templateUrl: './changelog.component.html',
styleUrls: ['./changelog.component.scss']
})
export class ChangelogComponent implements OnInit {
timestamp = new Date(); // Time from date input
pageSize = 25;
changelog: ChangelogModel[] = [];
modalDetail = 0; // Index of changelog element to show details of
timestamp = new Date(); // Time from date input
pageSize = 25;
changelog: ChangelogModel[] = [];
modalDetail = 0; // Index of changelog element to show details of
constructor(
private api: ApiService,
private modal: ModalService
) { }
constructor(
private api: ApiService,
private modal: ModalService
) { }
ngOnInit(): void {
this.loadChangelog();
}
ngOnInit(): void {
this.loadChangelog();
}
loadChangelog(page = 0) { // Load changelog with page no relative to current page
this.api.get<ChangelogModel[]>(`/changelog/${
page > 0 ? this.changelog[0]._id : // Use id if no new date was given
Math.floor(new Date(
new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000 // Adjust timezone
).getTime() / 1000).toString(16) + '0000000000000000' // Id from time
}/${page}/${this.pageSize}`, data => {
this.changelog = data.map(e => new ChangelogModel().deserialize(e));
if (page) { // Adjust date picker to new first element when user clicked on next page
this.timestamp = new Date(this.changelog[0].date);
}
});
}
loadChangelog(page = 0) { // Load changelog with page no relative to current page
this.api.get<ChangelogModel[]>(`/changelog/${
page > 0 ? this.changelog[0]._id : // Use id if no new date was given
Math.floor(new Date(
new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000 // Adjust timezone
).getTime() / 1000).toString(16) + '0000000000000000' // Id from time
}/${page}/${this.pageSize}`, data => {
this.changelog = data.map(e => new ChangelogModel().deserialize(e));
if (page) { // Adjust date picker to new first element when user clicked on next page
this.timestamp = new Date(this.changelog[0].date);
}
});
}
// Show details of a changelog element with reference to needed modal
showDetails(i: number, modal: TemplateRef<any>) {
this.modalDetail = i;
this.modal.open(modal).then(() => {});
}
// Show details of a changelog element with reference to needed modal
showDetails(i: number, modal: TemplateRef<any>) {
this.modalDetail = i;
this.modal.open(modal).then(() => {});
}
}

View File

@ -1,50 +1,50 @@
<p>
Bosch IoT Cloud space where all applications are hosted:
<a href="https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2">
https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2
</a><br>
Find the API documentation here:
<a href="https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/">
https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/
</a><br>
Admin database management page:
<a href="https://definma-db.apps.de1.bosch-iot-cloud.com/">
https://definma-db.apps.de1.bosch-iot-cloud.com/
</a><br>
Code repository UI
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui">
https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui
</a><br>
Code repository API
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api">
https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api
</a><br>
Code repository Models
<a href="https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models">
https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models
</a><br>
Bosch IoT Cloud space where all applications are hosted:
<a href="https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2">
https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2
</a><br>
Find the API documentation here:
<a href="https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/">
https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/
</a><br>
Admin database management page:
<a href="https://definma-db.apps.de1.bosch-iot-cloud.com/">
https://definma-db.apps.de1.bosch-iot-cloud.com/
</a><br>
Code repository UI
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui">
https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui
</a><br>
Code repository API
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api">
https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api
</a><br>
Code repository Models
<a href="https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models">
https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models
</a><br>
</p>
<p>
All applications are hosted in the bosch IoT Cloud. The API is hosted in a Node.js container, with a bound MongoDB
instance. <br>
The Angular UI is hosted in a staticfile container, serving the built Angular bundle. <br>
Finally any machine learning models are hosted in Python containers.
All applications are hosted in the bosch IoT Cloud. The API is hosted in a Node.js container, with a bound MongoDB
instance. <br>
The Angular UI is hosted in a staticfile container, serving the built Angular bundle. <br>
Finally any machine learning models are hosted in Python containers.
</p>
<h4>Development setup</h4>
<p>
In any case, it is good to have PxProxy working, following the
<a href="https://inside-docupedia.bosch.com/confluence/pages/viewpage.action?pageId=608652557">
Bosch installation guide
</a>. <br>
For pushing to the BIC, you need the CloudFoundry CLI as described in the
<a href="https://inside-docupedia.bosch.com/confluence/display/BICI/Cloud+Foundry+CLI">BIC documentation</a>.
If the the downloaded version of the CLI should not work, there is an old installer with a definitely working version
on the file share in the bin folder.
Please consider that you need to have a login to the BIC and be marked as space developer for the DeFinMa Org. <br>
For front and back end development, you need a
<a href="https://www.mongodb.com/try/download/community">local MongoDB server</a> as well as
<a href="https://nodejs.org/en/">Node.js</a>.
In any case, it is good to have PxProxy working, following the
<a href="https://inside-docupedia.bosch.com/confluence/pages/viewpage.action?pageId=608652557">
Bosch installation guide
</a>. <br>
For pushing to the BIC, you need the CloudFoundry CLI as described in the
<a href="https://inside-docupedia.bosch.com/confluence/display/BICI/Cloud+Foundry+CLI">BIC documentation</a>.
If the the downloaded version of the CLI should not work, there is an old installer with a definitely working version
on the file share in the bin folder.
Please consider that you need to have a login to the BIC and be marked as space developer for the DeFinMa Org. <br>
For front and back end development, you need a
<a href="https://www.mongodb.com/try/download/community">local MongoDB server</a> as well as
<a href="https://nodejs.org/en/">Node.js</a>.
</p>

View File

@ -1,15 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-documentation-architecture',
templateUrl: './documentation-architecture.component.html',
styleUrls: ['./documentation-architecture.component.scss']
selector: 'app-documentation-architecture',
templateUrl: './documentation-architecture.component.html',
styleUrls: ['./documentation-architecture.component.scss']
})
export class DocumentationArchitectureComponent implements OnInit {
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -1,38 +1,38 @@
<p>
The used database instance is a MongoDB instance running on the BIC, storing all application data.
The admin database management page can be accessed <a href="https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/">here</a>.
However, it is recommended to use a dedicated MongoDB application for anything apart from a quick look.
The two tested applications are <a href="https://www.mongodb.com/products/compass">MongoDB Compass</a> and
<a href="https://robomongo.org/s">Robo 3T</a> (recommended for trying queries).
The used database instance is a MongoDB instance running on the BIC, storing all application data.
The admin database management page can be accessed <a href="https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/">here</a>.
However, it is recommended to use a dedicated MongoDB application for anything apart from a quick look.
The two tested applications are <a href="https://www.mongodb.com/products/compass">MongoDB Compass</a> and
<a href="https://robomongo.org/s">Robo 3T</a> (recommended for trying queries).
</p>
<p>
To connect to the BIC instance from your local application follow the
<a href="https://inside-docupedia.bosch.com/confluence/display/BICI/MongoDB+Client+Tool+via+SSH+Tunnel">BIC guide</a>.
<br>
TL;DR:
To connect to the BIC instance from your local application follow the
<a href="https://inside-docupedia.bosch.com/confluence/display/BICI/MongoDB+Client+Tool+via+SSH+Tunnel">BIC guide</a>.
<br>
TL;DR:
</p>
<p>For the first time:</p>
<ul>
<li>cf login</li>
<li>cf enable-ssh definma-api</li>
<li>cf create-service-key definmadb &lt;key name, can be whatever&gt;</li>
<li>cf login</li>
<li>cf enable-ssh definma-api</li>
<li>cf create-service-key definmadb &lt;key name, can be whatever&gt;</li>
</ul>
<p>Every time:</p>
<ul>
<li>cf ssh -L 1120:st0cvm200118.internal-mongodb.de1.bosch-iot-cloud.com:30000 definma-api</li>
<li>
Login into your application with localhost:1120 and use credentials and authentication database from the BIC
(Data can be found at Home &gt; DeFinMa &gt; production &gt; definma-api &gt; Services &gt; dots on the right of definmadb &gt; View Credentials)
</li>
<li>cf ssh -L 1120:st0cvm200118.internal-mongodb.de1.bosch-iot-cloud.com:30000 definma-api</li>
<li>
Login into your application with localhost:1120 and use credentials and authentication database from the BIC
(Data can be found at Home &gt; DeFinMa &gt; production &gt; definma-api &gt; Services &gt; dots on the right of definmadb &gt; View Credentials)
</li>
</ul>
<h4>Backup Instructions</h4>
<p>
For creating a database backup, you must follow the same steps from above (except the last one).
Additionally you need the <a href="https://www.mongodb.com/try/download/database-tools">MongoDB Tools</a> installed.
Extract the ZIP file to a location on your hard drive and launch a Git Bash in that directory.
For creating a database backup, you must follow the same steps from above (except the last one).
Additionally you need the <a href="https://www.mongodb.com/try/download/database-tools">MongoDB Tools</a> installed.
Extract the ZIP file to a location on your hard drive and launch a Git Bash in that directory.
</p>
<p>To back up the database from the BIC:</p>
@ -51,377 +51,377 @@
</pre>
<p>
More information can be found inside the
<a href="https://docs.mongodb.com/database-tools/">documentation</a>. <br>
The BIC service also creates an internal backup, which can be requested to restore, see
<a href="https://inside-docupedia.bosch.com/confluence/pages/viewpage.action?pageId=565402281">MongoDB FAQ</a>
More information can be found inside the
<a href="https://docs.mongodb.com/database-tools/">documentation</a>. <br>
The BIC service also creates an internal backup, which can be requested to restore, see
<a href="https://inside-docupedia.bosch.com/confluence/pages/viewpage.action?pageId=565402281">MongoDB FAQ</a>
</p>
<h4>Database Model</h4>
<app-img-magnifier src="assets/imgs/db_structure_latest.svg" zoom="2" [magnifierSize]="{width: 400, height: 300}"
id="db-structure"></app-img-magnifier>
id="db-structure"></app-img-magnifier>
<h4>Field Reference</h4>
<rb-table class="field-reference">
<tr><th>samples</th><th></th><th>Example</th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63c98d1c020f8cda6e06'</td>
</tr>
<tr>
<td>type</td>
<td>
The material status of the sample, can be either <span class="name">as-delivered/raw</span> or
<span class="name">processed</span>.
</td>
<td>'processed'</td>
</tr>
<tr>
<td>number</td>
<td>The sample number, generated by the server for new samples</td>
<td>'An31'</td>
</tr>
<tr>
<td>color</td>
<td>Sample color</td>
<td>'black'</td>
</tr>
<tr>
<td>batch</td>
<td>Batch number the sample was from</td>
<td>'2264486614'</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr>
<td>condition</td>
<td>sample condition with <span class="name">condition_template</span> reference and all fields defined by the
template</td>
<td>{{'{'}}condition_template: '5f3151b5b8a886007d2de9ed', time in minutes: 30{{'}'}}</td>
</tr>
<tr>
<td>material_id</td>
<td>Reference to the sample material</td>
<td>'5f2e63118d1c020f8cda6a0a'</td>
</tr>
<tr>
<td>note_id</td>
<td>Reference to the sample note</td>
<td>'5f2e63c98d1c020f8cda6e08'</td>
</tr>
<tr>
<td>user_id</td>
<td>Reference to the user who created the sample</td>
<td>'5f294dd4aa9ea5085c7d7315'</td>
</tr>
<tr><th>samples</th><th></th><th>Example</th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63c98d1c020f8cda6e06'</td>
</tr>
<tr>
<td>type</td>
<td>
The material status of the sample, can be either <span class="name">as-delivered/raw</span> or
<span class="name">processed</span>.
</td>
<td>'processed'</td>
</tr>
<tr>
<td>number</td>
<td>The sample number, generated by the server for new samples</td>
<td>'An31'</td>
</tr>
<tr>
<td>color</td>
<td>Sample color</td>
<td>'black'</td>
</tr>
<tr>
<td>batch</td>
<td>Batch number the sample was from</td>
<td>'2264486614'</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr>
<td>condition</td>
<td>sample condition with <span class="name">condition_template</span> reference and all fields defined by the
template</td>
<td>{{'{'}}condition_template: '5f3151b5b8a886007d2de9ed', time in minutes: 30{{'}'}}</td>
</tr>
<tr>
<td>material_id</td>
<td>Reference to the sample material</td>
<td>'5f2e63118d1c020f8cda6a0a'</td>
</tr>
<tr>
<td>note_id</td>
<td>Reference to the sample note</td>
<td>'5f2e63c98d1c020f8cda6e08'</td>
</tr>
<tr>
<td>user_id</td>
<td>Reference to the user who created the sample</td>
<td>'5f294dd4aa9ea5085c7d7315'</td>
</tr>
<tr><th>notes</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70cc'</td>
</tr>
<tr>
<td>comment</td>
<td>General remarks</td>
<td>'stabilized'</td>
</tr>
<tr>
<td>sample_references</td>
<td>Array of references to other samples, each reference containing the referenced <span class="name">sample_id
</span> as well as a <span class="name">relation</span> field describing the relationship</td>
<td>{{'{'}}sample_id: '5f2e63d68d1c020f8cda701c', relation: 'belongs to'{{'}'}}</td>
</tr>
<tr>
<td>custom_fields</td>
<td>Additional information as key value pairs for the sample, making it easier to process this information</td>
<td>{{'{'}}vwz: '0 min'{{'}'}}</td>
</tr>
<tr><th>notes</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70cc'</td>
</tr>
<tr>
<td>comment</td>
<td>General remarks</td>
<td>'stabilized'</td>
</tr>
<tr>
<td>sample_references</td>
<td>Array of references to other samples, each reference containing the referenced <span class="name">sample_id
</span> as well as a <span class="name">relation</span> field describing the relationship</td>
<td>{{'{'}}sample_id: '5f2e63d68d1c020f8cda701c', relation: 'belongs to'{{'}'}}</td>
</tr>
<tr>
<td>custom_fields</td>
<td>Additional information as key value pairs for the sample, making it easier to process this information</td>
<td>{{'{'}}vwz: '0 min'{{'}'}}</td>
</tr>
<tr><th>note_fields</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70ce'</td>
</tr>
<tr>
<td>name</td>
<td>name of the <span class="name">custom_fields</span> key</td>
<td>'test series'</td>
</tr>
<tr>
<td>qty</td>
<td>number of notes with this key</td>
<td>24</td>
</tr>
<tr><th>note_fields</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70ce'</td>
</tr>
<tr>
<td>name</td>
<td>name of the <span class="name">custom_fields</span> key</td>
<td>'test series'</td>
</tr>
<tr>
<td>qty</td>
<td>number of notes with this key</td>
<td>24</td>
</tr>
<tr><th>materials</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70d0'</td>
</tr>
<tr>
<td>name</td>
<td>Trade name of the material</td>
<td>'Ultradur B4300 G6'</td>
</tr>
<tr>
<td>numbers</td>
<td>Bosch material part numbers</td>
<td>['5515753021, '5515753404']</td>
</tr>
<tr>
<td>properties</td>
<td>material class specific properties with <span class="name">material_template</span> reference and all fields
defined by the template</td>
<td>{{'{'}}material_template: '5f2e89874ac96c007fb06e83', mineral: 0, glass_fiber: 30, carbon_fiber: 0{{'}'}}</td>
</tr>
<tr>
<td>group_id</td>
<td>Reference to the material group</td>
<td>'5f2e631191c5d68f8a0708c4'</td>
</tr>
<tr>
<td>supplier_id</td>
<td>Reference to the material supplier</td>
<td>'5f2e631191c5d68f8a0708c7'</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>materials</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63e98d1c020f8cda70d0'</td>
</tr>
<tr>
<td>name</td>
<td>Trade name of the material</td>
<td>'Ultradur B4300 G6'</td>
</tr>
<tr>
<td>numbers</td>
<td>Bosch material part numbers</td>
<td>['5515753021, '5515753404']</td>
</tr>
<tr>
<td>properties</td>
<td>material class specific properties with <span class="name">material_template</span> reference and all fields
defined by the template</td>
<td>{{'{'}}material_template: '5f2e89874ac96c007fb06e83', mineral: 0, glass_fiber: 30, carbon_fiber: 0{{'}'}}</td>
</tr>
<tr>
<td>group_id</td>
<td>Reference to the material group</td>
<td>'5f2e631191c5d68f8a0708c4'</td>
</tr>
<tr>
<td>supplier_id</td>
<td>Reference to the material supplier</td>
<td>'5f2e631191c5d68f8a0708c7'</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>material_groups</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e631291c5d68f8a0708f9'</td>
</tr>
<tr>
<td>name</td>
<td>The chemical material type</td>
<td>'PA66'</td>
</tr>
<tr><th>material_groups</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e631291c5d68f8a0708f9'</td>
</tr>
<tr>
<td>name</td>
<td>The chemical material type</td>
<td>'PA66'</td>
</tr>
<tr><th>material_supplier</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e631991c5d68f8a0709c3'</td>
</tr>
<tr>
<td>name</td>
<td>The material supplier</td>
<td>'BASF'</td>
</tr>
<tr><th>material_supplier</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e631991c5d68f8a0709c3'</td>
</tr>
<tr>
<td>name</td>
<td>The material supplier</td>
<td>'BASF'</td>
</tr>
<tr><th>measurements</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f294d25aa9ea5085c7d7305'</td>
</tr>
<tr>
<td>sample_id</td>
<td>Reference to the sample this measurement belongs to</td>
<td>'5f2e63c98d1c020f8cda6e06'</td>
</tr>
<tr>
<td>measurement_template</td>
<td>Reference to the Template defining the structure of the measurement values</td>
<td>'5f294d25aa9ea5085c7d7305'</td>
</tr>
<tr>
<td>values</td>
<td>Measurement values in defined format</td>
<td>{{'{'}}vn: 100.4{{'}'}}</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>measurements</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f294d25aa9ea5085c7d7305'</td>
</tr>
<tr>
<td>sample_id</td>
<td>Reference to the sample this measurement belongs to</td>
<td>'5f2e63c98d1c020f8cda6e06'</td>
</tr>
<tr>
<td>measurement_template</td>
<td>Reference to the Template defining the structure of the measurement values</td>
<td>'5f294d25aa9ea5085c7d7305'</td>
</tr>
<tr>
<td>values</td>
<td>Measurement values in defined format</td>
<td>{{'{'}}vn: 100.4{{'}'}}</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>&lt;collection&gt;_templates</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63ee8d1c020f8cda7128'</td>
</tr>
<tr>
<td>name</td>
<td>Display name of the template</td>
<td>'spectrum'</td>
</tr>
<tr>
<td>version</td>
<td>Version number of the template</td>
<td>2</td>
</tr>
<tr>
<td>first_id</td>
<td>Reference to the first instance of this template with <span class="name">version</span> number 1</td>
<td>'5f2e89bb4ac96c007fb06e86'</td>
</tr>
<tr>
<td>parameters</td>
<td>Specified parameters of this template. The <span class="name">name</span> property is used as the key in the
document using this template, the range can have the following properties: <span class="name">min</span> specifies
the minimum numeric value, <span class="name">max</span> specifies the maximum numeric value, <span class="name">
values</span> specifies an array of allowed values of this parameter and <span class="name">type: 'array'</span>
specifies that this parameter must be an array</td>
<td></td>
</tr>
<tr><th>&lt;collection&gt;_templates</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63ee8d1c020f8cda7128'</td>
</tr>
<tr>
<td>name</td>
<td>Display name of the template</td>
<td>'spectrum'</td>
</tr>
<tr>
<td>version</td>
<td>Version number of the template</td>
<td>2</td>
</tr>
<tr>
<td>first_id</td>
<td>Reference to the first instance of this template with <span class="name">version</span> number 1</td>
<td>'5f2e89bb4ac96c007fb06e86'</td>
</tr>
<tr>
<td>parameters</td>
<td>Specified parameters of this template. The <span class="name">name</span> property is used as the key in the
document using this template, the range can have the following properties: <span class="name">min</span> specifies
the minimum numeric value, <span class="name">max</span> specifies the maximum numeric value, <span class="name">
values</span> specifies an array of allowed values of this parameter and <span class="name">type: 'array'</span>
specifies that this parameter must be an array</td>
<td></td>
</tr>
<tr><th>users</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63cc8d1c020f8cda6e6a'</td>
</tr>
<tr>
<td>name</td>
<td>The username</td>
<td>'admin'</td>
</tr>
<tr>
<td>email</td>
<td>The user's email address used for password reset</td>
<td>'test@bosch.com'</td>
</tr>
<tr>
<td>location</td>
<td>The abbreviation of the Bosch site of the user</td>
<td>'Rng'</td>
</tr>
<tr>
<td>level</td>
<td>The permission level, can be either <span class="name">read</span>, <span class="name">write</span>,
<span class="name">dev</span> or <span class="name">admin</span>. The exact level permissions can be found at the
<a routerLink="/documentation">general documentation</a></td>
<td></td>
</tr>
<tr>
<td>devices</td>
<td>Array of all spectrum measurement devices the user has access to</td>
<td>['Rng01', 'Rng02']</td>
</tr>
<tr>
<td>pass</td>
<td>The user's password in hashed form using bcrypt</td>
<td>'$2a$10$m8DqvZR3plZEv8EPwPo7Luvyrm/ZQDiPzfBh6bpU/1XFWOGONkJyG'</td>
</tr>
<tr>
<td>key</td>
<td>The API key, generated when the user is created</td>
<td>'5f294dd4aa9ea5085c7d7314'</td>
</tr>
<tr>
<td>models</td>
<td>
Reference to the prediction models the user should have access to. Ignored for <span class="name">dev</span> and
<span class="name">admin</span> as they automatically have access to all models.
</td>
<td>['5f466fb1e566810dd8b3e919', '5f294d8aaa9ea5085c7d730b']</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>users</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63cc8d1c020f8cda6e6a'</td>
</tr>
<tr>
<td>name</td>
<td>The username</td>
<td>'admin'</td>
</tr>
<tr>
<td>email</td>
<td>The user's email address used for password reset</td>
<td>'test@bosch.com'</td>
</tr>
<tr>
<td>location</td>
<td>The abbreviation of the Bosch site of the user</td>
<td>'Rng'</td>
</tr>
<tr>
<td>level</td>
<td>The permission level, can be either <span class="name">read</span>, <span class="name">write</span>,
<span class="name">dev</span> or <span class="name">admin</span>. The exact level permissions can be found at the
<a routerLink="/documentation">general documentation</a></td>
<td></td>
</tr>
<tr>
<td>devices</td>
<td>Array of all spectrum measurement devices the user has access to</td>
<td>['Rng01', 'Rng02']</td>
</tr>
<tr>
<td>pass</td>
<td>The user's password in hashed form using bcrypt</td>
<td>'$2a$10$m8DqvZR3plZEv8EPwPo7Luvyrm/ZQDiPzfBh6bpU/1XFWOGONkJyG'</td>
</tr>
<tr>
<td>key</td>
<td>The API key, generated when the user is created</td>
<td>'5f294dd4aa9ea5085c7d7314'</td>
</tr>
<tr>
<td>models</td>
<td>
Reference to the prediction models the user should have access to. Ignored for <span class="name">dev</span> and
<span class="name">admin</span> as they automatically have access to all models.
</td>
<td>['5f466fb1e566810dd8b3e919', '5f294d8aaa9ea5085c7d730b']</td>
</tr>
<tr>
<td>status</td>
<td>Status of the document, can be either <span class="name">new</span>, <span class="name">validated</span> or
<span class="name">deleted</span>.</td>
<td>'new'</td>
</tr>
<tr><th>models</th><th></th><th></th></tr>
<tr>
<td>group</td>
<td>group the model belongs to</td>
<td>'VN'</td>
</tr>
<tr>
<td>models</td>
<td>models of the group with _id, name and url to the Python endpoint</td>
<td>{{'{'}}_id: '5f466fb1e566810dd8b3e919', name: POM, url: 'http://localhost:9099/test'{{'}'}}</td>
</tr>
<tr><th>models</th><th></th><th></th></tr>
<tr>
<td>group</td>
<td>group the model belongs to</td>
<td>'VN'</td>
</tr>
<tr>
<td>models</td>
<td>models of the group with _id, name and url to the Python endpoint</td>
<td>{{'{'}}_id: '5f466fb1e566810dd8b3e919', name: POM, url: 'http://localhost:9099/test'{{'}'}}</td>
</tr>
<tr><th>model_files</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f294d47aa9ea5085c7d7308'</td>
</tr>
<tr>
<td>name</td>
<td>The name of the model</td>
<td>'humidity-1'</td>
</tr>
<tr>
<td>data</td>
<td>The Python model data in binary format</td>
<td>&lt;binary data&gt;</td>
</tr>
<tr><th>model_files</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f294d47aa9ea5085c7d7308'</td>
</tr>
<tr>
<td>name</td>
<td>The name of the model</td>
<td>'humidity-1'</td>
</tr>
<tr>
<td>data</td>
<td>The Python model data in binary format</td>
<td>&lt;binary data&gt;</td>
</tr>
<tr><th>changelogs</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63cc8d1c020f8cda6e6e'</td>
</tr>
<tr>
<td>action</td>
<td>The URL which invoked the database write access</td>
<td>'POST /material/new'</td>
</tr>
<tr>
<td>collection_name</td>
<td>Collection that was written to</td>
<td>'material_groups'</td>
</tr>
<tr>
<td>conditions</td>
<td>Condition arguments used when accessing the database</td>
<td>{{'{'}}id: '5f2e63118d1c020f8cda6a0a'{{'}'}}</td>
</tr>
<tr>
<td>data</td>
<td>data which was written to the database</td>
<td>{{'{'}}name: 'PBT'{{'}'}}</td>
</tr>
<tr>
<td>user_id</td>
<td>The user that executed this command</td>
<td>'5f2e63118d1c020f8cda6a09'</td>
</tr>
<tr><th>changelogs</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63cc8d1c020f8cda6e6e'</td>
</tr>
<tr>
<td>action</td>
<td>The URL which invoked the database write access</td>
<td>'POST /material/new'</td>
</tr>
<tr>
<td>collection_name</td>
<td>Collection that was written to</td>
<td>'material_groups'</td>
</tr>
<tr>
<td>conditions</td>
<td>Condition arguments used when accessing the database</td>
<td>{{'{'}}id: '5f2e63118d1c020f8cda6a0a'{{'}'}}</td>
</tr>
<tr>
<td>data</td>
<td>data which was written to the database</td>
<td>{{'{'}}name: 'PBT'{{'}'}}</td>
</tr>
<tr>
<td>user_id</td>
<td>The user that executed this command</td>
<td>'5f2e63118d1c020f8cda6a09'</td>
</tr>
<tr><th>help</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63d28d1c020f8cda6f86'</td>
</tr>
<tr>
<td>key</td>
<td>The key used to find the required help text</td>
<td>'/documentation/database'</td>
</tr>
<tr>
<td>level</td>
<td>The minimum level required to read this help</td>
<td>'write'</td>
</tr>
<tr>
<td>text</td>
<td>The actual help text</td>
<td>'This page documents the database.'</td>
</tr>
<tr><th>help</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>
<td>'5f2e63d28d1c020f8cda6f86'</td>
</tr>
<tr>
<td>key</td>
<td>The key used to find the required help text</td>
<td>'/documentation/database'</td>
</tr>
<tr>
<td>level</td>
<td>The minimum level required to read this help</td>
<td>'write'</td>
</tr>
<tr>
<td>text</td>
<td>The actual help text</td>
<td>'This page documents the database.'</td>
</tr>
</rb-table>

View File

@ -1,14 +1,14 @@
.field-reference td:nth-child(3) {
font-family: boschmono, monospace;
font-family: boschmono, monospace;
}
span.name {
font-style: italic;
font-style: italic;
}
pre {
padding: 1rem;
color: #212529;
background: #e6e6e6;
white-space: pre-wrap;
padding: 1rem;
color: #212529;
background: #e6e6e6;
white-space: pre-wrap;
}

View File

@ -1,15 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-documentation-database',
templateUrl: './documentation-database.component.html',
styleUrls: ['./documentation-database.component.scss']
selector: 'app-documentation-database',
templateUrl: './documentation-database.component.html',
styleUrls: ['./documentation-database.component.scss']
})
export class DocumentationDatabaseComponent implements OnInit {
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -1,38 +1,38 @@
<h4>Model file upload</h4>
<p>
Model files have to be saved as a .pkl file in 80_Modelle_BIC. <br>
For upload the upload script has to be opened (can be in every environment). The name of the model file has to be
entered wih the .pkl ending in the first command and without .pkl in the second.
Model files have to be saved as a .pkl file in 80_Modelle_BIC. <br>
For upload the upload script has to be opened (can be in every environment). The name of the model file has to be
entered wih the .pkl ending in the first command and without .pkl in the second.
</p>
<h4 class="space-above">Adding new model scripts</h4>
<p>
Open Spyder in the base environment and open the model.py within.
Open Spyder in the base environment and open the model.py within.
</p>
<ul>
<li>Enter model file name without .pkl in fetchData</li>
<li>Add new prediction function below others</li>
<li>duplicate another @app.route and rename according to desired route name and prediction function name</li>
<li>Enter model file name without .pkl in fetchData</li>
<li>Add new prediction function below others</li>
<li>duplicate another @app.route and rename according to desired route name and prediction function name</li>
</ul>
<h4 class="space-above">Testing locally</h4>
<p>
To test the model Python script locally, you need to execute it in the base environment, in which all necessary
packages have to be installed. Note that Spyder also has to be opened in the base environment.
To test the model Python script locally, you need to execute it in the base environment, in which all necessary
packages have to be installed. Note that Spyder also has to be opened in the base environment.
</p>
<ul>
<li>Open a separate Anaconda Prompt and cd into the definma-UI folder</li>
<li>Adapt the model-mock.json at definma-UI/assets/ and enter the new model URL and name</li>
<li>Execute &quot;python -m http.server&quot; in the Anaconda Prompt</li>
<li>Execute the model.py script in Spyder</li>
<li>Navigate to <a href="http://localhost:8000">http://localhost:8000</a> in the browser</li>
<li>If there are problems with cached data, open the Browser developer console (Ctrl+Shift+I) and tick
&quot;Disable cache&quot; in the Network tab. The console then has to stay open.</li>
<li>Open a separate Anaconda Prompt and cd into the definma-UI folder</li>
<li>Adapt the model-mock.json at definma-UI/assets/ and enter the new model URL and name</li>
<li>Execute &quot;python -m http.server&quot; in the Anaconda Prompt</li>
<li>Execute the model.py script in Spyder</li>
<li>Navigate to <a href="http://localhost:8000">http://localhost:8000</a> in the browser</li>
<li>If there are problems with cached data, open the Browser developer console (Ctrl+Shift+I) and tick
&quot;Disable cache&quot; in the Network tab. The console then has to stay open.</li>
</ul>
<h4 class="space-above">Model script upload</h4>
@ -40,17 +40,17 @@
In the Windows Powershell:
<ul>
<li>cf login (only at the first time)</li>
<li>API endpoint: https://api.sys.de1.bosch-iot-cloud.com</li>
<li>Enter email with .de.bosch.com</li>
<li>Enter BIC password</li>
<li>Select space to upload to (currently 3 - development)</li>
<li>cd into the folder containing the manifest.yaml (here is also model.py)</li>
<li>cf push</li>
<li>cf login (only at the first time)</li>
<li>API endpoint: https://api.sys.de1.bosch-iot-cloud.com</li>
<li>Enter email with .de.bosch.com</li>
<li>Enter BIC password</li>
<li>Select space to upload to (currently 3 - development)</li>
<li>cd into the folder containing the manifest.yaml (here is also model.py)</li>
<li>cf push</li>
</ul>
<p>
After upload the new model details have to be entered in the UI.
After upload the new model details have to be entered in the UI.
</p>

View File

@ -1,15 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-documentation-models',
templateUrl: './documentation-models.component.html',
styleUrls: ['./documentation-models.component.scss']
selector: 'app-documentation-models',
templateUrl: './documentation-models.component.html',
styleUrls: ['./documentation-models.component.scss']
})
export class DocumentationModelsComponent implements OnInit {
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -1,78 +1,78 @@
<p>
<a [href]="api.hostName + '/static/intro-presentation/index.html'">
View the presentation explaining the main functions
</a><br>
View the <a href="/assets/docs/Veit-Lukas_T3000.pdf">T3000 report</a> and
<a href="/assets/docs/Veit-Lukas_Bachelor-Thesis.pdf">Bachelor Thesis</a> for an in-depth application description.
<a [href]="api.hostName + '/static/intro-presentation/index.html'">
View the presentation explaining the main functions
</a><br>
View the <a href="/assets/docs/Veit-Lukas_T3000.pdf">T3000 report</a> and
<a href="/assets/docs/Veit-Lukas_Bachelor-Thesis.pdf">Bachelor Thesis</a> for an in-depth application description.
</p>
<h4>User levels</h4>
<rb-table>
<tr>
<th></th>
<th>prediction</th>
<th>read</th>
<th>write</th>
<th>dev</th>
<th>admin</th>
</tr>
<tr>
<th>use prediction models</th>
<td>specified ones</td>
<td>specified ones</td>
<td>specified ones</td>
<td>all</td>
<td>all</td>
</tr>
<tr>
<th>read sample data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>add samples/edit own</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>read spectral data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>edit other's data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>maintain templates</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>edit users</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th></th>
<th>prediction</th>
<th>read</th>
<th>write</th>
<th>dev</th>
<th>admin</th>
</tr>
<tr>
<th>use prediction models</th>
<td>specified ones</td>
<td>specified ones</td>
<td>specified ones</td>
<td>all</td>
<td>all</td>
</tr>
<tr>
<th>read sample data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>add samples/edit own</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>read spectral data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>edit other's data</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>maintain templates</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
<tr>
<th>edit users</th>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-abort-frame"></span></td>
<td><span class="rb-ic rb-ic-checkmark-frame"></span></td>
</tr>
</rb-table>

View File

@ -1,17 +1,17 @@
@import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors";
p {
margin-bottom: 20px;
margin-bottom: 20px;
}
img#db-structure {
width: 100%;
width: 100%;
}
.rb-ic-checkmark-frame {
color: $brand-success;
color: $brand-success;
}
.rb-ic-abort-frame {
color: $brand-danger;
color: $brand-danger;
}

View File

@ -3,17 +3,17 @@ import {ApiService} from '../services/api.service';
@Component({
selector: 'app-documentation',
templateUrl: './documentation.component.html',
styleUrls: ['./documentation.component.scss']
selector: 'app-documentation',
templateUrl: './documentation.component.html',
styleUrls: ['./documentation.component.scss']
})
export class DocumentationComponent implements OnInit {
constructor(
public api: ApiService
) { }
constructor(
public api: ApiService
) { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -1,11 +1,11 @@
<rb-alert alertTitle="Error" type="error" okBtnLabel="Discard">
<div class="space-below">
{{message}}
</div>
<div *ngIf="details.length">
<a href="javascript:" class="rb-details-toggle" rbDetailsToggle #triggerDetails="rbDetailsToggle">Details</a>
<div *ngIf="triggerDetails.open">
<p *ngFor="let detail of details">{{detail}}</p>
</div>
</div>
<div class="space-below">
{{message}}
</div>
<div *ngIf="details.length">
<a href="javascript:" class="rb-details-toggle" rbDetailsToggle #triggerDetails="rbDetailsToggle">Details</a>
<div *ngIf="triggerDetails.open">
<p *ngFor="let detail of details">{{detail}}</p>
</div>
</div>
</rb-alert>

View File

@ -1,18 +1,18 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss']
selector: 'app-error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss']
})
export class ErrorComponent implements OnInit {
message = ''; // Main error message
details: string[] = []; // Array of error detail paragraphs
message = ''; // Main error message
details: string[] = []; // Array of error detail paragraphs
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -2,8 +2,8 @@ import { ExistsPipe } from './exists.pipe';
describe('ExistsPipe', () => {
it('create an instance', () => {
const pipe = new ExistsPipe();
expect(pipe).toBeTruthy();
});
it('create an instance', () => {
const pipe = new ExistsPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,13 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'exists',
pure: true
name: 'exists',
pure: true
})
export class ExistsPipe implements PipeTransform {
transform(value: unknown, key?): unknown {
return value || value === 0 ? (key ? value[key] : value) : '';
}
transform(value: unknown, key?): unknown {
return value || value === 0 ? (key ? value[key] : value) : '';
}
}

View File

@ -1,25 +1,25 @@
<h3>Help
<span *ngIf="login.isLevel.dev" class="rb-ic rb-ic-edit clickable space-left" (click)="edit = true"></span>
<span *ngIf="login.isLevel.dev" class="rb-ic rb-ic-edit clickable space-left" (click)="edit = true"></span>
</h3>
<div *ngIf="edit; else normalView">
<rb-form-select label="level" [(ngModel)]="content.level">
<option value="none">none</option>
<option *ngFor="let level of login.levels" [value]="level">{{level}}</option>
</rb-form-select>
<rb-form-textarea label="text" [(ngModel)]="content.text"></rb-form-textarea>
<rb-icon-button icon="save" mode="primary" (click)="saveHelp()">Save</rb-icon-button>
<rb-icon-button icon="delete" mode="danger" (click)="deleteHelp()" class="delete-btn">Delete</rb-icon-button>
<rb-form-select label="level" [(ngModel)]="content.level">
<option value="none">none</option>
<option *ngFor="let level of login.levels" [value]="level">{{level}}</option>
</rb-form-select>
<rb-form-textarea label="text" [(ngModel)]="content.text"></rb-form-textarea>
<rb-icon-button icon="save" mode="primary" (click)="saveHelp()">Save</rb-icon-button>
<rb-icon-button icon="delete" mode="danger" (click)="deleteHelp()" class="delete-btn">Delete</rb-icon-button>
</div>
<ng-template #normalView>
<p *ngIf="content.text; else defaultContent" class="content-text">
{{content.text}}
</p>
<ng-template #defaultContent>
<ng-container *ngIf="content.text === ''">
Sadly, currently there is no help available for this page. Please contact
<a [href]="'mailto:' + d.contact.mail">{{d.contact.name}}</a> for further questions.
</ng-container>
</ng-template>
<p *ngIf="content.text; else defaultContent" class="content-text">
{{content.text}}
</p>
<ng-template #defaultContent>
<ng-container *ngIf="content.text === ''">
Sadly, currently there is no help available for this page. Please contact
<a [href]="'mailto:' + d.contact.mail">{{d.contact.name}}</a> for further questions.
</ng-container>
</ng-template>
</ng-template>

View File

@ -1,7 +1,7 @@
.delete-btn {
float: right;
float: right;
}
.content-text {
white-space: pre-line;
white-space: pre-line;
}

View File

@ -6,48 +6,48 @@ import {HelpModel} from '../models/help.model';
import {LoginService} from '../services/login.service';
@Component({
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.scss']
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.scss']
})
export class HelpComponent implements OnInit {
content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'}); // Help content
edit = false; // Set true to change to edit mode
private route = ''; // URIComponent encoded route which serves as a key to fetch the help document
content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'}); // Help content
edit = false; // Set true to change to edit mode
private route = ''; // URIComponent encoded route which serves as a key to fetch the help document
constructor(
private router: Router,
public d: DataService,
private api: ApiService,
public login: LoginService
) { }
constructor(
private router: Router,
public d: DataService,
private api: ApiService,
public login: LoginService
) { }
ngOnInit(): void {
// Remove ids from path
this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, ''));
this.api.get<HelpModel>('/help/' + this.route, (data, err) => {
if (!err) { // Content was found
this.content = new HelpModel().deserialize(data);
}
else {
this.content.text = '';
}
});
}
ngOnInit(): void {
// Remove ids from path
this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, ''));
this.api.get<HelpModel>('/help/' + this.route, (data, err) => {
if (!err) { // Content was found
this.content = new HelpModel().deserialize(data);
}
else {
this.content.text = '';
}
});
}
saveHelp() {
this.api.post('/help/' + this.route, this.content.sendFormat(), () => {
this.edit = false;
});
}
saveHelp() {
this.api.post('/help/' + this.route, this.content.sendFormat(), () => {
this.edit = false;
});
}
deleteHelp() {
this.api.delete('/help/' + this.route, (ignore, err) => {
if (!err) {
this.content = new HelpModel().deserialize({text: null, level: 'none'});
this.edit = false;
}
});
}
deleteHelp() {
this.api.delete('/help/' + this.route, (ignore, err) => {
if (!err) {
this.content = new HelpModel().deserialize({text: null, level: 'none'});
this.edit = false;
}
});
}
}

View File

@ -1,20 +1,20 @@
<div *ngIf="!login.isLoggedIn">
<app-login></app-login>
<img src="/assets/imgs/key-visual.png" alt="" class="key-visual">
<app-login></app-login>
<img src="/assets/imgs/key-visual.png" alt="" class="key-visual">
</div>
<div *ngIf="login.isLoggedIn">
<rb-form-multi-select name="groupSelect" idField="id" [items]="keys" [(ngModel)]="isActiveKey" label="Groups" class="selection">
<span *rbFormMultiSelectOption="let key">{{key.id}}</span>
<rb-form-multi-select name="groupSelect" idField="id" [items]="keys" [(ngModel)]="isActiveKey" label="Groups" class="selection">
<span *rbFormMultiSelectOption="let key">{{key.id}}</span>
</rb-form-multi-select>
<rb-icon-button icon="forward-right" mode="primary" (click)="updateGroups(isActiveKey)">
Apply groups
</rb-icon-button>
</rb-form-multi-select>
<rb-icon-button icon="forward-right" mode="primary" (click)="updateGroups(isActiveKey)">
Apply groups
</rb-icon-button>
<div id="divChart">
<canvas id="myChart">
</canvas>
</div>
<div id="divChart">
<canvas id="myChart">
</canvas>
</div>
</div>

View File

@ -1,17 +1,17 @@
app-login {
float: left;
float: left;
}
.key-visual {
width: 70%;
float: right;
width: 70%;
float: right;
}
.selection{
float: left;
width: 20%;
float: left;
width: 20%;
}
rb-form-multi-select {
width: 20%;
width: 20%;
}

View File

@ -5,138 +5,138 @@ import { Chart } from 'chart.js';
interface KeyInterface {
id: string;
count: number;
active: boolean;
id: string;
count: number;
active: boolean;
}
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
keys: KeyInterface[] = [];
isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active
myChart: Chart;
keys: KeyInterface[] = [];
isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active
myChart: Chart;
constructor(
public login: LoginService,
public api: ApiService
) { }
constructor(
public login: LoginService,
public api: ApiService
) { }
ngOnInit() {
// Fetch all available groups
this.fetchData('/material/groups', data => this.createGroup(data));
}
ngOnInit() {
// Fetch all available groups
this.fetchData('/material/groups', data => this.createGroup(data));
}
// Api access with callback
async fetchData(URL: string, processor: any) {
this.api.get(URL, (sData, err, headers) => {
processor(sData);
});
}
// Api access with callback
async fetchData(URL: string, processor: any) {
this.api.get(URL, (sData, err, headers) => {
processor(sData);
});
}
// Fill interface with data
createGroup(data: any) {
let temp: KeyInterface[] = [];
// Fill interface with data
createGroup(data: any) {
let temp: KeyInterface[] = [];
for (var i = 0; i < data.length; i++) {
temp.push({ id: data[i], count: 0, active: false });
}
this.keys = temp; // Invoke update in rb-multiselect
this.initChart();
for (var i = 0; i < data.length; i++) {
temp.push({ id: data[i], count: 0, active: false });
}
this.keys = temp; // Invoke update in rb-multiselect
this.initChart();
// Only neccesary if keys get preselected
//this.calcFieldSelectKeys();
// Only neccesary if keys get preselected
//this.calcFieldSelectKeys();
// Fetch all samples populated with according group
this.getSamples();
}
// Fetch all samples populated with according group
this.getSamples();
}
// Iterate through active keys to assemble an api request for the required data
getSamples() {
let query = '/samples?status%5B%5D=validated&status=new&filters%5B%5D=%7B%22mode%22%3A%22in%22%2C%22field%22%3A%22material.group%22%2C%22values%22%3A%5B';
let temp = '';
this.keys.forEach(key => {
temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // Replace split().join() with replaceAll()
});
if (temp === '') {
this.countSamples('');
} else {
query = query + temp.substr(0, temp.length - 3) + '%5D%7D&fields%5B%5D=material.group';
this.fetchData(query, data => this.countSamples(data));
}
}
// Iterate through active keys to assemble an api request for the required data
getSamples() {
let query = '/samples?status%5B%5D=validated&status=new&filters%5B%5D=%7B%22mode%22%3A%22in%22%2C%22field%22%3A%22material.group%22%2C%22values%22%3A%5B';
let temp = '';
this.keys.forEach(key => {
temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // Replace split().join() with replaceAll()
});
if (temp === '') {
this.countSamples('');
} else {
query = query + temp.substr(0, temp.length - 3) + '%5D%7D&fields%5B%5D=material.group';
this.fetchData(query, data => this.countSamples(data));
}
}
// Loop through samples and count
countSamples(data: any) {
this.keys.map(key => key.count = 0);
for (var i = 0; i < data.length; i++) {
this.keys.forEach(key => {
if (key.id === data[i].material.group) {
key.count += 1;
}
});
}
this.updateGraph();
}
// Loop through samples and count
countSamples(data: any) {
this.keys.map(key => key.count = 0);
for (var i = 0; i < data.length; i++) {
this.keys.forEach(key => {
if (key.id === data[i].material.group) {
key.count += 1;
}
});
}
this.updateGraph();
}
// Preset select
calcFieldSelectKeys() {
this.keys.forEach(key => {
this.isActiveKey[key.id] = key.active;
});
}
// Preset select
calcFieldSelectKeys() {
this.keys.forEach(key => {
this.isActiveKey[key.id] = key.active;
});
}
// Update keys based on select
updateGroups(activeKeys: any) {
this.keys.forEach(key => {
if (activeKeys.hasOwnProperty(key.id)) {
key.active = activeKeys[key.id];
}
});
this.getSamples();
}
// Update keys based on select
updateGroups(activeKeys: any) {
this.keys.forEach(key => {
if (activeKeys.hasOwnProperty(key.id)) {
key.active = activeKeys[key.id];
}
});
this.getSamples();
}
// Get data for graph based on active keys
updateGraph() {
let nameList: string[] = [];
let dataList: number[] = [];
// Get data for graph based on active keys
updateGraph() {
let nameList: string[] = [];
let dataList: number[] = [];
this.keys.forEach(key => {
if (key.active) {
nameList.push(key.id);
dataList.push(key.count);
}
})
this.myChart.data.labels = nameList;
this.myChart.data.datasets[0].data = dataList;
this.myChart.update();
}
this.keys.forEach(key => {
if (key.active) {
nameList.push(key.id);
dataList.push(key.count);
}
})
this.myChart.data.labels = nameList;
this.myChart.data.datasets[0].data = dataList;
this.myChart.update();
}
// Initialize graph
async initChart() {
this.myChart = new Chart("myChart", {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Number of samples per group',
data: [],
backgroundColor: 'rgba(0, 86, 145, 1)'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
}
// Initialize graph
async initChart() {
this.myChart = new Chart("myChart", {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Number of samples per group',
data: [],
backgroundColor: 'rgba(0, 86, 145, 1)'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
}
}

View File

@ -1,17 +1,17 @@
<div class="img-container">
<div class="magnifier"
[ngStyle]="{
'background-size': backgroundSize,
'background-image': 'url(\'' + src + '\')',
'background-position': '-' + magnifierPos.x * zoom + 'px -' + magnifierPos.y * zoom + 'px',
left: magnifierPos.x + 'px',
top: magnifierPos.y + 'px',
width: magnifierSize.width + 'px',
height: magnifierSize.height + 'px'
}"
(mousemove)="calcPos($event)"
(mouseleave)="showMagnifier = false"
*ngIf="showMagnifier">
</div>
<img [src]="src" alt="" (mousemove)="calcPos($event)" (mouseenter)="showMagnifier = true" #mainImg>
<div class="magnifier"
[ngStyle]="{
'background-size': backgroundSize,
'background-image': 'url(\'' + src + '\')',
'background-position': '-' + magnifierPos.x * zoom + 'px -' + magnifierPos.y * zoom + 'px',
left: magnifierPos.x + 'px',
top: magnifierPos.y + 'px',
width: magnifierSize.width + 'px',
height: magnifierSize.height + 'px'
}"
(mousemove)="calcPos($event)"
(mouseleave)="showMagnifier = false"
*ngIf="showMagnifier">
</div>
<img [src]="src" alt="" (mousemove)="calcPos($event)" (mouseenter)="showMagnifier = true" #mainImg>
</div>

View File

@ -1,18 +1,18 @@
@import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors";
.img-container {
position:relative;
overflow: hidden;
position:relative;
overflow: hidden;
& > img {
width: 100%;
}
& > img {
width: 100%;
}
}
.magnifier {
position: absolute;
background: #FFF no-repeat -500px -500px;
z-index: 99;
border: 1px solid #FFF;
box-shadow: 10px 10px 25px $color-bosch-light-gray-b25;
position: absolute;
background: #FFF no-repeat -500px -500px;
z-index: 99;
border: 1px solid #FFF;
box-shadow: 10px 10px 25px $color-bosch-light-gray-b25;
}

View File

@ -1,48 +1,48 @@
import {AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
@Component({
selector: 'app-img-magnifier',
templateUrl: './img-magnifier.component.html',
styleUrls: ['./img-magnifier.component.scss']
selector: 'app-img-magnifier',
templateUrl: './img-magnifier.component.html',
styleUrls: ['./img-magnifier.component.scss']
})
export class ImgMagnifierComponent implements OnInit, AfterViewInit {
@Input() src: string; // Image source
@Input() zoom: number; // Zoom level
@Input() magnifierSize: {width: number, height: number}; // Size of the magnifier
@ViewChild('mainImg') mainImg: ElementRef;
@Input() src: string; // Image source
@Input() zoom: number; // Zoom level
@Input() magnifierSize: {width: number, height: number}; // Size of the magnifier
@ViewChild('mainImg') mainImg: ElementRef;
backgroundSize;
magnifierPos = {x: 0, y: 0}; // Position of the magnifier
showMagnifier = false;
backgroundSize;
magnifierPos = {x: 0, y: 0}; // Position of the magnifier
showMagnifier = false;
constructor(
private window: Window
) { }
constructor(
private window: Window
) { }
ngOnInit(): void {
}
ngOnInit(): void {
}
ngAfterViewInit() {
setTimeout(() => {
this.calcBackgroundSize();
}, 1);
}
ngAfterViewInit() {
setTimeout(() => {
this.calcBackgroundSize();
}, 1);
}
calcPos(event) { // Calculate the current magnifier position
const img = this.mainImg.nativeElement.getBoundingClientRect();
this.magnifierPos.x = Math.min(
img.width - this.magnifierSize.width,
Math.max(0, event.pageX - img.left - this.window.pageXOffset - this.magnifierSize.width / 2)
);
this.magnifierPos.y = Math.min(
img.height - this.magnifierSize.height + 7,
Math.max(0, event.pageY - img.top - this.window.pageYOffset - this.magnifierSize.height / 2)
);
}
calcPos(event) { // Calculate the current magnifier position
const img = this.mainImg.nativeElement.getBoundingClientRect();
this.magnifierPos.x = Math.min(
img.width - this.magnifierSize.width,
Math.max(0, event.pageX - img.left - this.window.pageXOffset - this.magnifierSize.width / 2)
);
this.magnifierPos.y = Math.min(
img.height - this.magnifierSize.height + 7,
Math.max(0, event.pageY - img.top - this.window.pageYOffset - this.magnifierSize.height / 2)
);
}
calcBackgroundSize() {
this.backgroundSize = this.mainImg ? (this.mainImg.nativeElement.width * this.zoom - this.magnifierSize.width) +
'px ' + (this.mainImg.nativeElement.height * this.zoom - this.magnifierSize.height) + 'px ' : '0 0';
}
calcBackgroundSize() {
this.backgroundSize = this.mainImg ? (this.mainImg.nativeElement.width * this.zoom - this.magnifierSize.width) +
'px ' + (this.mainImg.nativeElement.height * this.zoom - this.magnifierSize.height) + 'px ' : '0 0';
}
}

View File

@ -1,25 +1,25 @@
<div class="login-wrapper">
<h2>Please log in</h2>
<h2>Please log in</h2>
<form #loginForm="ngForm">
<rb-form-input name="username" label="username" appValidate="username" required [(ngModel)]="username"
#usernameInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{usernameInput.errors.failure}}</ng-template>
</rb-form-input>
<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>
</rb-form-input>
<rb-form-input *ngIf="passreset" type="email" name="email" label="email" email required [(ngModel)]="email"
#emailInput="ngModel">
<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)="userLogin()" type="submit"
[disabled]="!loginForm.form.valid">
{{passreset ? 'Send' : 'Login'}}
</button>
<div class="message">{{message}}</div>
</form>
<form #loginForm="ngForm">
<rb-form-input name="username" label="username" appValidate="username" required [(ngModel)]="username"
#usernameInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{usernameInput.errors.failure}}</ng-template>
</rb-form-input>
<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>
</rb-form-input>
<rb-form-input *ngIf="passreset" type="email" name="email" label="email" email required [(ngModel)]="email"
#emailInput="ngModel">
<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)="userLogin()" type="submit"
[disabled]="!loginForm.form.valid">
{{passreset ? 'Send' : 'Login'}}
</button>
<div class="message">{{message}}</div>
</form>
</div>

View File

@ -1,17 +1,17 @@
.login-wrapper {
max-width: 250px;
max-width: 250px;
}
.message {
font-size: 13px;
margin-top: 10px;
font-size: 13px;
margin-top: 10px;
}
.login-button {
margin-right: 10px;
margin-right: 10px;
}
.forgot-pass {
display: block;
margin-bottom: 1rem;
display: block;
margin-bottom: 1rem;
}

View File

@ -6,57 +6,57 @@ import {ApiService} from '../services/api.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
username = ''; // Credentials
password = '';
email = '';
message = ''; // Message below login fields
passreset = false; // To toggle between normal login and password reset form
username = ''; // Credentials
password = '';
email = '';
message = ''; // Message below login fields
passreset = false; // To toggle between normal login and password reset form
@ViewChild('loginForm') loginForm;
@ViewChild('loginForm') loginForm;
constructor(
private validate: ValidationService,
private login: LoginService,
private api: ApiService,
private router: Router
) { }
constructor(
private validate: ValidationService,
private login: LoginService,
private api: ApiService,
private router: Router
) { }
ngOnInit() {
}
ngOnInit() {
}
userLogin() {
if (this.passreset) { // Reset password
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.login.login(this.username, this.password).then(ok => {
if (ok) {
this.message = 'Login successful';
if (this.login.isLevel.read) {
this.router.navigate(['/samples']);
}
else { // Navigate prediction users to prediction as they cannot access samples
this.router.navigate(['/prediction']);
}
}
else {
this.message = 'Wrong credentials!';
}
});
}
}
userLogin() {
if (this.passreset) { // Reset password
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.login.login(this.username, this.password).then(ok => {
if (ok) {
this.message = 'Login successful';
if (this.login.isLevel.read) {
this.router.navigate(['/samples']);
}
else { // Navigate prediction users to prediction as they cannot access samples
this.router.navigate(['/prediction']);
}
}
else {
this.message = 'Wrong credentials!';
}
});
}
}
}

View File

@ -1,65 +1,65 @@
<h2>{{material.name | exists}}</h2>
<form #materialForm="ngForm" *ngIf="!loading">
<rb-form-input name="materialname" label="product name" appValidate="stringNin" [appValidateArgs]="[materialNames]"
required [(ngModel)]="material.name" #materialnameInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{materialnameInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="supplier" label="supplier"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialSuppliers)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.supplier" #supplierInput="ngModel"
(focusout)="checkTypo($event, 'materialSuppliers', 'supplier', modalWarning)"
title="material supplier, eg. BASF">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="group" label="group"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialGroups)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.group" #groupInput="ngModel"
(focusout)="checkTypo($event, 'materialGroups', 'group', modalWarning)"
title="chemical material type, eg. PA66">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="materialname" label="product name" appValidate="stringNin" [appValidateArgs]="[materialNames]"
required [(ngModel)]="material.name" #materialnameInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{materialnameInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="supplier" label="supplier"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialSuppliers)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.supplier" #supplierInput="ngModel"
(focusout)="checkTypo($event, 'materialSuppliers', 'supplier', modalWarning)"
title="material supplier, eg. BASF">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="group" label="group"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialGroups)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.group" #groupInput="ngModel"
(focusout)="checkTypo($event, 'materialGroups', 'group', modalWarning)"
title="chemical material type, eg. PA66">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
</rb-form-input>
<ng-template #modalWarning>
<rb-alert alertTitle="Warning" type="warning" okBtnLabel="Use suggestion" cancelBtnLabel="Keep value">
The specified {{modalText.list}} could not be found in the list. <br>
Did you mean {{modalText.suggestion}}?
</rb-alert>
</ng-template>
<rb-array-input [(ngModel)]="material.numbers" name="materialNumbers" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" [rbArrayInputListener]="'materialNumber'" [index]="item.i"
label="material number" appValidate="string" [name]="'materialNumber-' + item.i"
[ngModel]="item.value" title="Bosch material part number, eg. 5515753021"></rb-form-input>
</rb-array-input>
<rb-form-select name="propertiesSelect" label="Type" title="=overall material group specific properties"
[(ngModel)]="material.properties.material_template">
<option *ngFor="let m of d.latest.materialTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<rb-form-input *ngFor="let parameter of
d.id.materialTemplates[material.properties.material_template].parameters;
index as i" [name]="'materialParameter' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="material.properties[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<ng-template #modalWarning>
<rb-alert alertTitle="Warning" type="warning" okBtnLabel="Use suggestion" cancelBtnLabel="Keep value">
The specified {{modalText.list}} could not be found in the list. <br>
Did you mean {{modalText.suggestion}}?
</rb-alert>
</ng-template>
<rb-array-input [(ngModel)]="material.numbers" name="materialNumbers" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" [rbArrayInputListener]="'materialNumber'" [index]="item.i"
label="material number" appValidate="string" [name]="'materialNumber-' + item.i"
[ngModel]="item.value" title="Bosch material part number, eg. 5515753021"></rb-form-input>
</rb-array-input>
<rb-form-select name="propertiesSelect" label="Type" title="=overall material group specific properties"
[(ngModel)]="material.properties.material_template">
<option *ngFor="let m of d.latest.materialTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<rb-form-input *ngFor="let parameter of
d.id.materialTemplates[material.properties.material_template].parameters;
index as i" [name]="'materialParameter' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="material.properties[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-icon-button icon="save" mode="primary" type="submit" (click)="materialSave()"
[disabled]="materialForm.form.invalid">
Save material
</rb-icon-button>
<rb-icon-button class="delete-material" icon="delete" mode="danger" (click)="deleteConfirm(modalDeleteConfirm)">
Delete sample
</rb-icon-button>
<rb-icon-button icon="save" mode="primary" type="submit" (click)="materialSave()"
[disabled]="materialForm.form.invalid">
Save material
</rb-icon-button>
<rb-icon-button class="delete-material" icon="delete" mode="danger" (click)="deleteConfirm(modalDeleteConfirm)">
Delete sample
</rb-icon-button>
</form>
<ng-template #modalDeleteConfirm>
<rb-alert alertTitle="Are you sure?" type="danger" [okBtnLabel]="'Delete material'"
cancelBtnLabel="Cancel">
Do you really want to delete {{material.name}}?
</rb-alert>
<rb-alert alertTitle="Are you sure?" type="danger" [okBtnLabel]="'Delete material'"
cancelBtnLabel="Cancel">
Do you really want to delete {{material.name}}?
</rb-alert>
</ng-template>

View File

@ -1,3 +1,3 @@
.delete-material {
float: right;
float: right;
}

View File

@ -11,132 +11,132 @@ import {ValidationService} from '../services/validation.service';
import {ErrorComponent} from '../error/error.component';
@Component({
selector: 'app-material',
templateUrl: './material.component.html',
styleUrls: ['./material.component.scss']
selector: 'app-material',
templateUrl: './material.component.html',
styleUrls: ['./material.component.scss']
})
export class MaterialComponent implements OnInit, AfterContentChecked {
@ViewChild('materialForm') materialForm: NgForm;
@ViewChild('materialForm') materialForm: NgForm;
material: MaterialModel; // Material to edit
materialNames: string[] = []; // All other material names for unique validation
material: MaterialModel; // Material to edit
materialNames: string[] = []; // All other material names for unique validation
modalText = {list: '', suggestion: ''}; // Modal for group and supplier correction
loading = 0; // Number of loading instances
checkFormAfterInit = true; // Revalidate all fields on the next AfterContentChecked
modalText = {list: '', suggestion: ''}; // Modal for group and supplier correction
loading = 0; // Number of loading instances
checkFormAfterInit = true; // Revalidate all fields on the next AfterContentChecked
constructor(
private api: ApiService,
private route: ActivatedRoute,
public d: DataService,
private modal: ModalService,
public autocomplete: AutocompleteService,
private router: Router,
private validation: ValidationService
) { }
constructor(
private api: ApiService,
private route: ActivatedRoute,
public d: DataService,
private modal: ModalService,
public autocomplete: AutocompleteService,
private router: Router,
private validation: ValidationService
) { }
ngOnInit(): void {
this.loading = 5;
this.api.get<MaterialModel>('/material/' + this.route.snapshot.paramMap.get('id'), data => {
this.material = new MaterialModel().deserialize(data);
this.loading--;
this.d.load('materials', () => {
// Filter out name of the edited material as it can stay the same
this.materialNames = this.d.arr.materials.map(e => e.name).filter(e => e !== this.material.name);
this.loading--;
});
});
this.d.load('materialSuppliers', () => {
this.loading--;
});
this.d.load('materialGroups', () => {
this.loading--;
});
this.d.load('materialTemplates', () => {
this.loading--;
});
}
ngOnInit(): void {
this.loading = 5;
this.api.get<MaterialModel>('/material/' + this.route.snapshot.paramMap.get('id'), data => {
this.material = new MaterialModel().deserialize(data);
this.loading--;
this.d.load('materials', () => {
// Filter out name of the edited material as it can stay the same
this.materialNames = this.d.arr.materials.map(e => e.name).filter(e => e !== this.material.name);
this.loading--;
});
});
this.d.load('materialSuppliers', () => {
this.loading--;
});
this.d.load('materialGroups', () => {
this.loading--;
});
this.d.load('materialTemplates', () => {
this.loading--;
});
}
ngAfterContentChecked() {
// Attach validators
if (this.materialForm && this.material.properties.material_template) { // Material template is set
this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => {
this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range);
});
}
ngAfterContentChecked() {
// Attach validators
if (this.materialForm && this.material.properties.material_template) { // Material template is set
this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => {
this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range);
});
}
// Revalidate
if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) {
this.checkFormAfterInit = false;
Object.keys(this.materialForm.form.controls).forEach(field => {
this.materialForm.form.get(field).updateValueAndValidity();
});
}
}
// Revalidate
if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) {
this.checkFormAfterInit = false;
Object.keys(this.materialForm.form.controls).forEach(field => {
this.materialForm.form.get(field).updateValueAndValidity();
});
}
}
// Attach validators specified in range to input with name
attachValidator(form, name: string, range: {[prop: string]: any}) {
if (form && form.form.get(name)) {
const validators = [];
if (range.hasOwnProperty('required')) {
validators.push(Validators.required);
}
if (range.hasOwnProperty('values')) {
validators.push(this.validation.generate('stringOf', [range.values]));
}
else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) {
validators.push(this.validation.generate('minMax', [range.min, range.max]));
}
else if (range.hasOwnProperty('min')) {
validators.push(this.validation.generate('min', [range.min]));
}
else if (range.hasOwnProperty('max')) {
validators.push(this.validation.generate('max', [range.max]));
}
form.form.get(name).setValidators(validators);
}
}
// Attach validators specified in range to input with name
attachValidator(form, name: string, range: {[prop: string]: any}) {
if (form && form.form.get(name)) {
const validators = [];
if (range.hasOwnProperty('required')) {
validators.push(Validators.required);
}
if (range.hasOwnProperty('values')) {
validators.push(this.validation.generate('stringOf', [range.values]));
}
else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) {
validators.push(this.validation.generate('minMax', [range.min, range.max]));
}
else if (range.hasOwnProperty('min')) {
validators.push(this.validation.generate('min', [range.min]));
}
else if (range.hasOwnProperty('max')) {
validators.push(this.validation.generate('max', [range.max]));
}
form.form.get(name).setValidators(validators);
}
}
materialSave() {
this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => {
delete this.d.arr.materials; // Reload materials
this.d.load('materials');
this.router.navigate(['/materials']);
});
}
materialSave() {
this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => {
delete this.d.arr.materials; // Reload materials
this.d.load('materials');
this.router.navigate(['/materials']);
});
}
deleteConfirm(modal) {
this.modal.open(modal).then(result => {
if (result) {
this.api.delete('/material/' + this.material._id, (ignore, error) => {
if (error) { // Material cannot be deleted as it is still referenced by active samples
const modalRef = this.modal.openComponent(ErrorComponent);
modalRef.instance.message = 'Cannot delete material as it is still in use!';
}
else {
this.router.navigate(['/materials']);
}
});
}
});
}
deleteConfirm(modal) {
this.modal.open(modal).then(result => {
if (result) {
this.api.delete('/material/' + this.material._id, (ignore, error) => {
if (error) { // Material cannot be deleted as it is still referenced by active samples
const modalRef = this.modal.openComponent(ErrorComponent);
modalRef.instance.message = 'Cannot delete material as it is still in use!';
}
else {
this.router.navigate(['/materials']);
}
});
}
});
}
checkTypo(event, list, mKey, modal: TemplateRef<any>) {
// User did not click on suggestion and entry is not in list
if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
this.d.arr[list].indexOf(this.material[mKey]) < 0) {
this.modalText.list = mKey;
this.modalText.suggestion = this.d.arr[list] // Find possible entry from list
.map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
.sort((a, b) => b.s - a.s)[0].v;
this.modal.open(modal).then(result => {
if (result) { // Use suggestion
this.material[mKey] = this.modalText.suggestion;
}
});
}
}
checkTypo(event, list, mKey, modal: TemplateRef<any>) {
// User did not click on suggestion and entry is not in list
if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
this.d.arr[list].indexOf(this.material[mKey]) < 0) {
this.modalText.list = mKey;
this.modalText.suggestion = this.d.arr[list] // Find possible entry from list
.map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
.sort((a, b) => b.s - a.s)[0].v;
this.modal.open(modal).then(result => {
if (result) { // Use suggestion
this.material[mKey] = this.modalText.suggestion;
}
});
}
}
}

View File

@ -1,91 +1,91 @@
<div class="header-addnew">
<rb-icon-button *ngIf="sampleSelect" mode="secondary" icon="close" (click)="sampleSelect = false"
class="validation-close" iconOnly></rb-icon-button>
<rb-icon-button [icon]="sampleSelect ? 'checkmark' : 'clear-all'"
mode="secondary" (click)="validate()">
{{sampleSelect ? 'Validate' : 'Validation'}}
</rb-icon-button>
<rb-icon-button *ngIf="sampleSelect" mode="secondary" icon="close" (click)="sampleSelect = false"
class="validation-close" iconOnly></rb-icon-button>
<rb-icon-button [icon]="sampleSelect ? 'checkmark' : 'clear-all'"
mode="secondary" (click)="validate()">
{{sampleSelect ? 'Validate' : 'Validation'}}
</rb-icon-button>
</div>
<div class="status-selection">
<label class="label">Status</label>
<rb-form-checkbox name="status-validated" [(ngModel)]="materialStatus.validated"
[disabled]="!materialStatus.new && !materialStatus.deleted"
(ngModelChange)="loadMaterials()">
validated
</rb-form-checkbox>
<rb-form-checkbox name="status-new" [(ngModel)]="materialStatus.new"
[disabled]="!materialStatus.validated && !materialStatus.deleted"
(ngModelChange)="loadMaterials()">
new
</rb-form-checkbox>
<rb-form-checkbox name="status-deleted" [(ngModel)]="materialStatus.deleted"
[disabled]="!materialStatus.validated && !materialStatus.new"
(ngModelChange)="loadMaterials()">
deleted
</rb-form-checkbox>
<label class="label">Status</label>
<rb-form-checkbox name="status-validated" [(ngModel)]="materialStatus.validated"
[disabled]="!materialStatus.new && !materialStatus.deleted"
(ngModelChange)="loadMaterials()">
validated
</rb-form-checkbox>
<rb-form-checkbox name="status-new" [(ngModel)]="materialStatus.new"
[disabled]="!materialStatus.validated && !materialStatus.deleted"
(ngModelChange)="loadMaterials()">
new
</rb-form-checkbox>
<rb-form-checkbox name="status-deleted" [(ngModel)]="materialStatus.deleted"
[disabled]="!materialStatus.validated && !materialStatus.new"
(ngModelChange)="loadMaterials()">
deleted
</rb-form-checkbox>
</div>
<div class="material-search space-right">
<rb-form-input label="search" [(ngModel)]="materialSearch" icon="close"></rb-form-input>
<span class="rb-ic rb-ic-close clickable" (click)="materialSearch = ''"></span>
<rb-form-input label="search" [(ngModel)]="materialSearch" icon="close"></rb-form-input>
<span class="rb-ic rb-ic-close clickable" (click)="materialSearch = ''"></span>
</div>
<ng-container *ngTemplateOutlet="paging"></ng-container>
<rb-table ellipsis scrollTop>
<tr>
<th *ngIf="sampleSelect">
<rb-form-checkbox name="select-all" (change)="selectAll($event)">all</rb-form-checkbox>
</th>
<th>Name</th>
<th>Supplier</th>
<th>Group</th>
<th *ngFor="let key of templateKeys">{{key.label}}</th>
<th>Numbers</th>
<th></th>
</tr>
<tr *ngFor="let material of (materials || []).filter(materialFilter(materialSearch))
.slice((page - 1) * pageSize, page * pageSize); index as i">
<td *ngIf="sampleSelect">
<rb-form-checkbox *ngIf="material.status !== 'deleted'" [name]="'validate-' + i"
[(ngModel)]="material.selected">
</rb-form-checkbox>
</td>
<td>{{material.name}}</td>
<td>{{material.supplier}}</td>
<td>{{material.group}}</td>
<td *ngFor="let key of templateKeys">{{material.properties[key.key] | exists}}</td>
<td>{{material.numbers}}</td>
<td>
<a [routerLink]="'/materials/edit/' + material._id" *ngIf="material.status !== 'deleted'">
<span class="rb-ic rb-ic-edit clickable"></span>
</a>
<span class="rb-ic rb-ic-undo clickable" *ngIf="material.status === 'deleted'"
(click)="restoreMaterial(material._id, restoreConfirm)"></span>
</td>
</tr>
<tr>
<th *ngIf="sampleSelect">
<rb-form-checkbox name="select-all" (change)="selectAll($event)">all</rb-form-checkbox>
</th>
<th>Name</th>
<th>Supplier</th>
<th>Group</th>
<th *ngFor="let key of templateKeys">{{key.label}}</th>
<th>Numbers</th>
<th></th>
</tr>
<tr *ngFor="let material of (materials || []).filter(materialFilter(materialSearch))
.slice((page - 1) * pageSize, page * pageSize); index as i">
<td *ngIf="sampleSelect">
<rb-form-checkbox *ngIf="material.status !== 'deleted'" [name]="'validate-' + i"
[(ngModel)]="material.selected">
</rb-form-checkbox>
</td>
<td>{{material.name}}</td>
<td>{{material.supplier}}</td>
<td>{{material.group}}</td>
<td *ngFor="let key of templateKeys">{{material.properties[key.key] | exists}}</td>
<td>{{material.numbers}}</td>
<td>
<a [routerLink]="'/materials/edit/' + material._id" *ngIf="material.status !== 'deleted'">
<span class="rb-ic rb-ic-edit clickable"></span>
</a>
<span class="rb-ic rb-ic-undo clickable" *ngIf="material.status === 'deleted'"
(click)="restoreMaterial(material._id, restoreConfirm)"></span>
</td>
</tr>
</rb-table>
<ng-container *ngTemplateOutlet="paging"></ng-container>
<ng-template #paging>
<div class="paging">
<button class="rb-btn rb-link" type="button" (click)="page = page - 1" [disabled]="page === 1">
<span class="rb-ic rb-ic-back-left"></span>
</button>
<rb-form-input label="page" [(ngModel)]="page"></rb-form-input>
<span>
of {{pages}}
</span>
<button class="rb-btn rb-link" type="button" (click)="page = page + 1" [disabled]="page >= pages">
<span class="rb-ic rb-ic-forward-right"></span>
</button>
</div>
<div class="paging">
<button class="rb-btn rb-link" type="button" (click)="page = page - 1" [disabled]="page === 1">
<span class="rb-ic rb-ic-back-left"></span>
</button>
<rb-form-input label="page" [(ngModel)]="page"></rb-form-input>
<span>
of {{pages}}
</span>
<button class="rb-btn rb-link" type="button" (click)="page = page + 1" [disabled]="page >= pages">
<span class="rb-ic rb-ic-forward-right"></span>
</button>
</div>
</ng-template>
<ng-template #restoreConfirm>
<rb-dialog dialogTitle="Restore sample">
Do you really want to restore this sample?
</rb-dialog>
<rb-dialog dialogTitle="Restore sample">
Do you really want to restore this sample?
</rb-dialog>
</ng-template>

View File

@ -1,63 +1,63 @@
.paging {
height: 50px;
float: left;
height: 50px;
float: left;
rb-form-input {
max-width: 65px;
}
rb-form-input {
max-width: 65px;
}
> * {
float: left;
}
> * {
float: left;
}
> button {
margin-top: 18px;
}
> button {
margin-top: 18px;
}
> span {
margin-top: 20px;
margin-left: 5px;
}
> span {
margin-top: 20px;
margin-left: 5px;
}
}
.status-selection {
overflow: hidden;
margin-bottom: 10px;
float: left;
margin-right: 15px;
overflow: hidden;
margin-bottom: 10px;
float: left;
margin-right: 15px;
label {
display: block;
font-weight: 700;
font-size: 10px;
}
label {
display: block;
font-weight: 700;
font-size: 10px;
}
rb-form-checkbox {
float: left;
margin-right: 10px;
margin-top: -10px;
}
rb-form-checkbox {
float: left;
margin-right: 10px;
margin-top: -10px;
}
}
.header-addnew {
& > * {
display: inline;
margin-bottom: 10px;
}
& > * {
display: inline;
margin-bottom: 10px;
}
rb-icon-button {
float: right;
}
rb-icon-button {
float: right;
}
}
.material-search {
width: 300px;
float: left;
position: relative;
width: 300px;
float: left;
position: relative;
span {
position: absolute;
right: 5px;
top: 24px;
}
span {
position: absolute;
right: 5px;
top: 24px;
}
}

View File

@ -6,92 +6,92 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components';
@Component({
selector: 'app-materials',
templateUrl: './materials.component.html',
styleUrls: ['./materials.component.scss']
selector: 'app-materials',
templateUrl: './materials.component.html',
styleUrls: ['./materials.component.scss']
})
export class MaterialsComponent implements OnInit {
materials: MaterialModel[] = []; // All materials
templateKeys: {key: string, label: string}[] = []; // Material template keys
materialStatus = {validated: true, new: true, deleted: false}; // Material statuses to show
materialSearch = ''; // Material name search string
sampleSelect = false; // Set to true to show checkboxes for validation
materials: MaterialModel[] = []; // All materials
templateKeys: {key: string, label: string}[] = []; // Material template keys
materialStatus = {validated: true, new: true, deleted: false}; // Material statuses to show
materialSearch = ''; // Material name search string
sampleSelect = false; // Set to true to show checkboxes for validation
page = 1; // Page settings
pages = 0;
pageSize = 25;
page = 1; // Page settings
pages = 0;
pageSize = 25;
constructor(
private api: ApiService,
public d: DataService,
private modal: ModalService
) { }
constructor(
private api: ApiService,
public d: DataService,
private modal: ModalService
) { }
ngOnInit(): void {
this.loadMaterials();
this.d.load('materialTemplates', () => {
this.d.arr.materialTemplates.forEach(template => {
template.parameters.forEach(parameter => {
this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`});
});
});
// Filter out duplicates
this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key));
});
}
ngOnInit(): void {
this.loadMaterials();
this.d.load('materialTemplates', () => {
this.d.arr.materialTemplates.forEach(template => {
template.parameters.forEach(parameter => {
this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`});
});
});
// Filter out duplicates
this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key));
});
}
loadMaterials() {
this.api.get<MaterialModel[]>('/materials?' +
Object.entries(this.materialStatus).filter(e => e[1]).map(e => 'status[]=' + e[0]).join('&'), data => {
this.materials = data.map(e => new MaterialModel().deserialize(e));
this.pages = Math.ceil(this.materials.length / this.pageSize);
this.page = 1;
});
}
loadMaterials() {
this.api.get<MaterialModel[]>('/materials?' +
Object.entries(this.materialStatus).filter(e => e[1]).map(e => 'status[]=' + e[0]).join('&'), data => {
this.materials = data.map(e => new MaterialModel().deserialize(e));
this.pages = Math.ceil(this.materials.length / this.pageSize);
this.page = 1;
});
}
validate() {
if (this.sampleSelect) { // Selection was done do actual validation
this.materials.forEach(sample => {
if (sample.selected) {
this.api.put('/material/validate/' + sample._id);
}
});
this.loadMaterials();
this.sampleSelect = false;
}
else { // Activate validation mode
this.sampleSelect = true;
}
}
validate() {
if (this.sampleSelect) { // Selection was done do actual validation
this.materials.forEach(sample => {
if (sample.selected) {
this.api.put('/material/validate/' + sample._id);
}
});
this.loadMaterials();
this.sampleSelect = false;
}
else { // Activate validation mode
this.sampleSelect = true;
}
}
selectAll(event) { // Toggle selection for all items except deleted ones
this.materials.forEach(material => {
if (material.status !== 'deleted') {
material.selected = event.target.checked;
}
else {
material.selected = false;
}
});
}
selectAll(event) { // Toggle selection for all items except deleted ones
this.materials.forEach(material => {
if (material.status !== 'deleted') {
material.selected = event.target.checked;
}
else {
material.selected = false;
}
});
}
restoreMaterial(id, modal) {
this.modal.open(modal).then(res => {
if (res) {
this.api.put('/sample/restore/' + id, {}, ignore => {
this.materials.find(e => e._id === id).status = 'new';
});
}
});
}
restoreMaterial(id, modal) {
this.modal.open(modal).then(res => {
if (res) {
this.api.put('/sample/restore/' + id, {}, ignore => {
this.materials.find(e => e._id === id).status = 'new';
});
}
});
}
ucFirst(string) { // Convert first character of string to uppercase
return string[0].toUpperCase() + string.slice(1);
}
ucFirst(string) { // Convert first character of string to uppercase
return string[0].toUpperCase() + string.slice(1);
}
materialFilter(ms) { // Filter function for material names
return e => e.name.indexOf(ms) >= 0;
}
materialFilter(ms) { // Filter function for material names
return e => e.name.indexOf(ms) >= 0;
}
}

View File

@ -1,73 +1,73 @@
<rb-icon-button icon="add" mode="primary" (click)="newModel = !newModel; oldModelGroup = ''" class="space-below">
New model
New model
</rb-icon-button>
<form *ngIf="newModel" #modelForm="ngForm">
<rb-form-input name="group" label="group" appValidate="string" required [(ngModel)]="modelGroup" #groupInput="ngModel"
[rbFormInputAutocomplete]="autocomplete.bind(this, groups)"
[rbDebounceTime]="0" [rbInitialOpen]="true">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-form-input name="name" label="name" appValidate="string" required [(ngModel)]="model.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="url" label="URL" appValidate="url" required [(ngModel)]="model.url" #urlInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{urlInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-form-input name="group" label="group" appValidate="string" required [(ngModel)]="modelGroup" #groupInput="ngModel"
[rbFormInputAutocomplete]="autocomplete.bind(this, groups)"
[rbDebounceTime]="0" [rbInitialOpen]="true">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-form-input name="name" label="name" appValidate="string" required [(ngModel)]="model.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="url" label="URL" appValidate="url" required [(ngModel)]="model.url" #urlInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{urlInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-icon-button icon="save" mode="primary" type="submit" [disabled]="!modelForm.form.valid" (click)="saveModel()">
Save model
</rb-icon-button>
<rb-icon-button icon="save" mode="primary" type="submit" [disabled]="!modelForm.form.valid" (click)="saveModel()">
Save model
</rb-icon-button>
</form>
<rb-table class="space-above space-below">
<tr>
<th>Name</th>
<th>URL</th>
<th></th>
<th></th>
</tr>
<tr>
<th>Name</th>
<th>URL</th>
<th></th>
<th></th>
</tr>
<ng-container *ngFor="let group of d.arr.modelGroups">
<tr><th>{{group.group}}</th><th></th><th></th><th></th><th></th></tr>
<tr *ngFor="let modelItem of group.models">
<td>{{modelItem.name}}</td>
<td>{{modelItem.url}}</td>
<td>
<span class="rb-ic rb-ic-edit clickable"
(click)="modelGroup = group.group;
oldModelGroup = group.group;
oldModelName = modelItem.name;
model = modelItem;
newModel = true;">
</span>
</td>
<td>
<span class="rb-ic rb-ic-delete clickable"
(click)="delete(modalDeleteConfirm, modelItem.name, group.group)"></span>
</td>
</tr>
</ng-container>
<ng-container *ngFor="let group of d.arr.modelGroups">
<tr><th>{{group.group}}</th><th></th><th></th><th></th><th></th></tr>
<tr *ngFor="let modelItem of group.models">
<td>{{modelItem.name}}</td>
<td>{{modelItem.url}}</td>
<td>
<span class="rb-ic rb-ic-edit clickable"
(click)="modelGroup = group.group;
oldModelGroup = group.group;
oldModelName = modelItem.name;
model = modelItem;
newModel = true;">
</span>
</td>
<td>
<span class="rb-ic rb-ic-delete clickable"
(click)="delete(modalDeleteConfirm, modelItem.name, group.group)"></span>
</td>
</tr>
</ng-container>
</rb-table>
<rb-table>
<tr>
<th>Model files</th>
<th></th>
<th></th>
</tr>
<tr *ngFor="let file of d.arr.modelFiles">
<td>{{file.name}}</td>
<td>{{file.size | size:'M'}}</td>
<td><span class="rb-ic rb-ic-delete clickable" (click)="delete(modalDeleteConfirm, file.name)"></span></td>
</tr>
<tr>
<th>Model files</th>
<th></th>
<th></th>
</tr>
<tr *ngFor="let file of d.arr.modelFiles">
<td>{{file.name}}</td>
<td>{{file.size | size:'M'}}</td>
<td><span class="rb-ic rb-ic-delete clickable" (click)="delete(modalDeleteConfirm, file.name)"></span></td>
</tr>
</rb-table>
<ng-template #modalDeleteConfirm>
<rb-alert alertTitle="Are you sure?" type="danger" okBtnLabel="Delete model" cancelBtnLabel="Cancel">
Do you really want to delete?
</rb-alert>
<rb-alert alertTitle="Are you sure?" type="danger" okBtnLabel="Delete model" cancelBtnLabel="Cancel">
Do you really want to delete?
</rb-alert>
</ng-template>

View File

@ -7,77 +7,77 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components';
import omit from 'lodash/omit';
@Component({
selector: 'app-model-templates',
templateUrl: './model-templates.component.html',
styleUrls: ['./model-templates.component.scss']
selector: 'app-model-templates',
templateUrl: './model-templates.component.html',
styleUrls: ['./model-templates.component.scss']
})
export class ModelTemplatesComponent implements OnInit {
newModel = false; // Display new model dialog
modelGroup = ''; // Group of the edited model
oldModelGroup = ''; // Group of the edited model before editing started
oldModelName = ''; // Name of the edited model before editing started
model = new ModelItemModel().models[0]; // Edited model
groups = []; // All model group names
newModel = false; // Display new model dialog
modelGroup = ''; // Group of the edited model
oldModelGroup = ''; // Group of the edited model before editing started
oldModelName = ''; // Name of the edited model before editing started
model = new ModelItemModel().models[0]; // Edited model
groups = []; // All model group names
constructor(
private api: ApiService,
public autocomplete: AutocompleteService,
public d: DataService,
private modal: ModalService
) { }
constructor(
private api: ApiService,
public autocomplete: AutocompleteService,
public d: DataService,
private modal: ModalService
) { }
ngOnInit(): void {
this.loadGroups();
}
ngOnInit(): void {
this.loadGroups();
}
loadGroups() {
delete this.d.arr.modelGroups;
this.d.load('modelGroups', () => {
this.groups = this.d.arr.modelGroups.map(e => e.group);
});
this.d.load('modelFiles');
}
loadGroups() {
delete this.d.arr.modelGroups;
this.d.load('modelGroups', () => {
this.groups = this.d.arr.modelGroups.map(e => e.group);
});
this.d.load('modelFiles');
}
saveModel() {
// Group was changed, delete model in old group
if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) {
this.delete(null, this.oldModelName, this.oldModelGroup);
}
this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => {
this.newModel = false; // Reset model edit parameters
this.loadGroups();
this.modelGroup = '';
this.oldModelGroup = '';
this.oldModelName = '';
this.model = new ModelItemModel().models[0];
});
}
saveModel() {
// Group was changed, delete model in old group
if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) {
this.delete(null, this.oldModelName, this.oldModelGroup);
}
this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => {
this.newModel = false; // Reset model edit parameters
this.loadGroups();
this.modelGroup = '';
this.oldModelGroup = '';
this.oldModelName = '';
this.model = new ModelItemModel().models[0];
});
}
delete(modal, name, group = null) {
new Promise(resolve => {
if (modal) { // If modal was given, wait for result
this.modal.open(modal).then(result => {
resolve(result);
});
}
else {
resolve(true);
}
}).then(res => {
if (res) {
if (group) { // Delete model group if given
this.api.delete(`/model/${group}/${name}`, () => {
this.loadGroups();
});
}
else { // Delete model file
this.api.delete(`/model/file/${name}`, () => {
this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1);
});
}
}
});
}
delete(modal, name, group = null) {
new Promise(resolve => {
if (modal) { // If modal was given, wait for result
this.modal.open(modal).then(result => {
resolve(result);
});
}
else {
resolve(true);
}
}).then(res => {
if (res) {
if (group) { // Delete model group if given
this.api.delete(`/model/${group}/${name}`, () => {
this.loadGroups();
});
}
else { // Delete model file
this.api.delete(`/model/file/${name}`, () => {
this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1);
});
}
}
});
}
}

View File

@ -1,7 +1,7 @@
import { BaseModel } from './base.model';
describe('BaseModel', () => {
it('should create an instance', () => {
expect(new BaseModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new BaseModel()).toBeTruthy();
});
});

View File

@ -1,10 +1,10 @@
export class BaseModel {
deserialize(input: any): this {
Object.assign(this, input);
return this;
}
deserialize(input: any): this {
Object.assign(this, input);
return this;
}
sendFormat(): this {
return this;
}
sendFormat(): this {
return this;
}
}

View File

@ -1,7 +1,7 @@
import { ChangelogModel } from './changelog.model';
describe('ChangelogModel', () => {
it('should create an instance', () => {
expect(new ChangelogModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new ChangelogModel()).toBeTruthy();
});
});

View File

@ -2,10 +2,10 @@ import {BaseModel} from './base.model';
import {IdModel} from './id.model';
export class ChangelogModel extends BaseModel {
_id: IdModel = null;
date: Date;
action: string;
collection: string;
conditions: {[key: string]: any};
data: {[key: string]: any};
_id: IdModel = null;
date: Date;
action: string;
collection: string;
conditions: {[key: string]: any};
data: {[key: string]: any};
}

View File

@ -1,7 +1,7 @@
import { CustomFieldsModel } from './custom-fields.model';
describe('CustomFieldsModel', () => {
it('should create an instance', () => {
expect(new CustomFieldsModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new CustomFieldsModel()).toBeTruthy();
});
});

View File

@ -2,6 +2,6 @@ import {BaseModel} from './base.model';
export class CustomFieldsModel extends BaseModel {
name = '';
qty = 0;
name = '';
qty = 0;
}

View File

@ -1,7 +1,7 @@
import { HelpModel } from './help.model';
describe('Help.Model', () => {
it('should create an instance', () => {
expect(new HelpModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new HelpModel()).toBeTruthy();
});
});

View File

@ -1,6 +1,6 @@
import {BaseModel} from './base.model';
export class HelpModel extends BaseModel {
text = '';
level = 'none';
text = '';
level = 'none';
}

View File

@ -1,7 +1,7 @@
import { MaterialModel } from './material.model';
describe('MaterialModel', () => {
it('should create an instance', () => {
expect(new MaterialModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new MaterialModel()).toBeTruthy();
});
});

View File

@ -3,16 +3,16 @@ import {IdModel} from './id.model';
import {BaseModel} from './base.model';
export class MaterialModel extends BaseModel {
_id: IdModel = null;
name = '';
supplier = '';
group = '';
properties: {material_template: string, [prop: string]: string} = {material_template: null};
numbers: string[] = [''];
selected = false;
status = '';
_id: IdModel = null;
name = '';
supplier = '';
group = '';
properties: {material_template: string, [prop: string]: string} = {material_template: null};
numbers: string[] = [''];
selected = false;
status = '';
sendFormat() {
return pick(this, ['name', 'supplier', 'group', 'numbers', 'properties']);
}
sendFormat() {
return pick(this, ['name', 'supplier', 'group', 'numbers', 'properties']);
}
}

View File

@ -1,7 +1,7 @@
import { MeasurementModel } from './measurement.model';
describe('MeasurementModel', () => {
it('should create an instance', () => {
expect(new MeasurementModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new MeasurementModel()).toBeTruthy();
});
});

View File

@ -4,28 +4,28 @@ import {IdModel} from './id.model';
import {BaseModel} from './base.model';
export class MeasurementModel extends BaseModel {
_id: IdModel = null;
sample_id: IdModel = null;
measurement_template: IdModel;
values: {[prop: string]: any} = {};
status = '';
_id: IdModel = null;
sample_id: IdModel = null;
measurement_template: IdModel;
values: {[prop: string]: any} = {};
status = '';
constructor(measurementTemplate: IdModel = null) {
super();
this.measurement_template = measurementTemplate;
}
constructor(measurementTemplate: IdModel = null) {
super();
this.measurement_template = measurementTemplate;
}
deserialize(input: any): this {
Object.assign(this, input);
Object.keys(this.values).forEach(key => {
if (this.values[key] === null) {
this.values[key] = '';
}
});
return this;
}
deserialize(input: any): this {
Object.assign(this, input);
Object.keys(this.values).forEach(key => {
if (this.values[key] === null) {
this.values[key] = '';
}
});
return this;
}
sendFormat(omitValues = []) {
return omit(pick(this, ['sample_id', 'measurement_template', 'values']), omitValues);
}
sendFormat(omitValues = []) {
return omit(pick(this, ['sample_id', 'measurement_template', 'values']), omitValues);
}
}

View File

@ -1,7 +1,7 @@
import { ModelFileModel } from './model-file.model';
describe('ModelFile.Model', () => {
it('should create an instance', () => {
expect(new ModelFileModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new ModelFileModel()).toBeTruthy();
});
});

View File

@ -1,6 +1,6 @@
import {BaseModel} from './base.model';
export class ModelFileModel extends BaseModel {
name = '';
size = 0;
name = '';
size = 0;
}

View File

@ -1,7 +1,7 @@
import { ModelItemModel } from './model-item.model';
describe('ModelItemModel', () => {
it('should create an instance', () => {
expect(new ModelItemModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new ModelItemModel()).toBeTruthy();
});
});

View File

@ -1,9 +1,9 @@
import {BaseModel} from './base.model';
export class ModelItemModel extends BaseModel {
group = '';
models = [{
name: '',
url: ''
}];
group = '';
models = [{
name: '',
url: ''
}];
}

View File

@ -1,7 +1,7 @@
import { SampleModel } from './sample.model';
describe('SampleModel', () => {
it('should create an instance', () => {
expect(new SampleModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new SampleModel()).toBeTruthy();
});
});

View File

@ -6,63 +6,63 @@ import {MeasurementModel} from './measurement.model';
import {BaseModel} from './base.model';
export class SampleModel extends BaseModel {
_id: IdModel = null;
color = '';
number = '';
type = '';
batch = '';
condition: {condition_template: string, [prop: string]: string} = {condition_template: null};
material_id: IdModel = null;
material: MaterialModel;
measurements: MeasurementModel[] = [];
note_id: IdModel = null;
user_id: IdModel = null;
selected = false;
notes: {
comment: string,
sample_references: {sample_id: IdModel, relation: string}[],
custom_fields: {[prop: string]: string}
} = {comment: '', sample_references: [], custom_fields: {}};
status = '';
added: Date = null;
_id: IdModel = null;
color = '';
number = '';
type = '';
batch = '';
condition: {condition_template: string, [prop: string]: string} = {condition_template: null};
material_id: IdModel = null;
material: MaterialModel;
measurements: MeasurementModel[] = [];
note_id: IdModel = null;
user_id: IdModel = null;
selected = false;
notes: {
comment: string,
sample_references: {sample_id: IdModel, relation: string}[],
custom_fields: {[prop: string]: string}
} = {comment: '', sample_references: [], custom_fields: {}};
status = '';
added: Date = null;
deserialize(input: any): this {
Object.assign(this, input);
if (input.hasOwnProperty('material')) {
this.material = new MaterialModel().deserialize(input.material);
this.material_id = input.material._id;
}
if (input.hasOwnProperty('measurements')) {
this.measurements = input.measurements.map(e => new MeasurementModel().deserialize(e));
}
if (input.hasOwnProperty('added')) {
this.added = new Date(input.added);
}
return this;
}
deserialize(input: any): this {
Object.assign(this, input);
if (input.hasOwnProperty('material')) {
this.material = new MaterialModel().deserialize(input.material);
this.material_id = input.material._id;
}
if (input.hasOwnProperty('measurements')) {
this.measurements = input.measurements.map(e => new MeasurementModel().deserialize(e));
}
if (input.hasOwnProperty('added')) {
this.added = new Date(input.added);
}
return this;
}
sendFormat(pickCondition = true) {
const pickFields = ['color', 'type', 'batch', 'material_id', 'notes'];
if (pickCondition) {
pickFields.push('condition');
}
const tmp = pick(this.conditionTemplateCheck(), pickFields);
Object.keys(tmp).forEach(key => {
if (tmp[key] === undefined) {
delete tmp[key];
}
});
if (this.material && this.material.name === undefined) {
delete tmp.material_id;
}
return tmp;
}
sendFormat(pickCondition = true) {
const pickFields = ['color', 'type', 'batch', 'material_id', 'notes'];
if (pickCondition) {
pickFields.push('condition');
}
const tmp = pick(this.conditionTemplateCheck(), pickFields);
Object.keys(tmp).forEach(key => {
if (tmp[key] === undefined) {
delete tmp[key];
}
});
if (this.material && this.material.name === undefined) {
delete tmp.material_id;
}
return tmp;
}
private conditionTemplateCheck() {
const res = cloneDeep(this);
if (res.condition.condition_template === null) {
res.condition = {};
}
return res;
}
private conditionTemplateCheck() {
const res = cloneDeep(this);
if (res.condition.condition_template === null) {
res.condition = {};
}
return res;
}
}

View File

@ -1,7 +1,7 @@
import { TemplateModel } from './template.model';
describe('TemplateModel', () => {
it('should create an instance', () => {
expect(new TemplateModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new TemplateModel()).toBeTruthy();
});
});

View File

@ -2,9 +2,9 @@ import {IdModel} from './id.model';
import {BaseModel} from './base.model';
export class TemplateModel extends BaseModel {
_id: IdModel = null;
name = '';
version = 0;
first_id: IdModel = null;
parameters: {name: string, range: {[prop: string]: any}, rangeString?: string}[] = [];
_id: IdModel = null;
name = '';
version = 0;
first_id: IdModel = null;
parameters: {name: string, range: {[prop: string]: any}, rangeString?: string}[] = [];
}

View File

@ -1,7 +1,7 @@
import { UserModel } from './user.model';
describe('UserModel', () => {
it('should create an instance', () => {
expect(new UserModel()).toBeTruthy();
});
it('should create an instance', () => {
expect(new UserModel()).toBeTruthy();
});
});

View File

@ -3,31 +3,31 @@ import {BaseModel} from './base.model';
import {IdModel} from './id.model';
export class UserModel extends BaseModel{
_id: IdModel = null;
name = '';
origName = '';
email = '';
level = '';
location = '';
devices = [''];
models = [''];
status = 'new';
edit = false;
_id: IdModel = null;
name = '';
origName = '';
email = '';
level = '';
location = '';
devices = [''];
models = [''];
status = 'new';
edit = false;
deserialize(input: any): this {
Object.assign(this, input);
this.origName = this.name;
return this;
}
deserialize(input: any): this {
Object.assign(this, input);
this.origName = this.name;
return this;
}
sendFormat(mode = 'user') {
const keys = ['name', 'email', 'location', 'devices'];
if (mode === 'admin') {
keys.push('level');
keys.push('models');
this.devices = this.devices.filter(e => e);
this.models = this.models.filter(e => e);
}
return pick(this, keys);
}
sendFormat(mode = 'user') {
const keys = ['name', 'email', 'location', 'devices'];
if (mode === 'admin') {
keys.push('level');
keys.push('models');
this.devices = this.devices.filter(e => e);
this.models = this.models.filter(e => e);
}
return pick(this, keys);
}
}

View File

@ -2,8 +2,8 @@ import { ObjectPipe } from './object.pipe';
describe('ObjectPipe', () => {
it('create an instance', () => {
const pipe = new ObjectPipe();
expect(pipe).toBeTruthy();
});
it('create an instance', () => {
const pipe = new ObjectPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -2,14 +2,14 @@ import { Pipe, PipeTransform } from '@angular/core';
import omit from 'lodash/omit';
@Pipe({
name: 'object',
pure: true
name: 'object',
pure: true
})
export class ObjectPipe implements PipeTransform {
transform(value: object, omitValue: string[] = []): string {
const res = omit(value, omitValue);
return res && Object.keys(res).length ? JSON.stringify(res) : '';
}
transform(value: object, omitValue: string[] = []): string {
const res = omit(value, omitValue);
return res && Object.keys(res).length ? JSON.stringify(res) : '';
}
}

View File

@ -2,8 +2,8 @@ import { ParametersPipe } from './parameters.pipe';
describe('ParametersPipe', () => {
it('create an instance', () => {
const pipe = new ParametersPipe();
expect(pipe).toBeTruthy();
});
it('create an instance', () => {
const pipe = new ParametersPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,13 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'parameters'
name: 'parameters'
})
export class ParametersPipe implements PipeTransform {
transform(value: {name: string, range: object}[]): string {
return `{${value.map(e => `${e.name}: <${JSON.stringify(e.range).replace('{}', 'any')
.replace(/["{}]/g, '')}>`).join(', ')}}`;
}
transform(value: {name: string, range: object}[]): string {
return `{${value.map(e => `${e.name}: <${JSON.stringify(e.range).replace('{}', 'any')
.replace(/["{}]/g, '')}>`).join(', ')}}`;
}
}

View File

@ -1,93 +1,93 @@
<rb-tab-panel (tabChanged)="groupChange($event)">
<ng-container *ngFor="let group of d.arr.modelGroups; index as i">
<div *rbTabPanelItem="group.group; id: i"></div>
</ng-container>
<ng-container *ngFor="let group of d.arr.modelGroups; index as i">
<div *rbTabPanelItem="group.group; id: i"></div>
</ng-container>
</rb-tab-panel>
<rb-form-select label="Model" (change)="result = undefined" [(ngModel)]="activeModelIndex">
<option *ngFor="let model of activeGroup.models; index as i" [value]="i">{{model.name}}</option>
<option *ngFor="let model of activeGroup.models; index as i" [value]="i">{{model.name}}</option>
</rb-form-select>
<div *ngIf="result" class="result" [@inOut]>
<ng-container *ngIf="multipleSamples; else singleSampleResult">
<h4 *ngFor="let prediction of result.predictions; index as i">
{{spectrumNames[i]}}:
<span *ngFor="let predictionEntry of prediction">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</h4>
</ng-container>
<ng-template #singleSampleResult>
<h4>
Average result:
<span *ngFor="let predictionEntry of result.mean">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}} &nbsp;{{( predictionEntry.std !== '' ? ' (standard deviation: '+ predictionEntry.std+')' : '') }}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</h4>
<a class="rb-details-toggle" rbDetailsToggle #triggerDetails="rbDetailsToggle">Details</a>
<div *ngIf="triggerDetails.open" class="space-below">
<p *ngFor="let prediction of result.predictions; index as i">
{{spectrumNames[i]}}:
<span *ngFor="let predictionEntry of prediction">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</p>
</div>
</ng-template>
<ng-container *ngIf="multipleSamples; else singleSampleResult">
<h4 *ngFor="let prediction of result.predictions; index as i">
{{spectrumNames[i]}}:
<span *ngFor="let predictionEntry of prediction">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</h4>
</ng-container>
<ng-template #singleSampleResult>
<h4>
Average result:
<span *ngFor="let predictionEntry of result.mean">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}} &nbsp;{{( predictionEntry.std !== '' ? ' (standard deviation: '+ predictionEntry.std+')' : '') }}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</h4>
<a class="rb-details-toggle" rbDetailsToggle #triggerDetails="rbDetailsToggle">Details</a>
<div *ngIf="triggerDetails.open" class="space-below">
<p *ngFor="let prediction of result.predictions; index as i">
{{spectrumNames[i]}}:
<span *ngFor="let predictionEntry of prediction">
{{predictionEntry.category}}&nbsp;<span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span>&nbsp;{{predictionEntry.label}}
</span>
<a [routerLink]='"."' fragment="disclaimer"><sup>#</sup></a>
</p>
</div>
</ng-template>
</div>
<div class="file-input space-below">
<rb-form-file name="spectrum-upload" label="spectrum file" maxSize="10000000" class="space-below" multiple
(ngModelChange)="fileToArray($event)" placeholder="Select file or drag and drop" dragDrop ngModel>
</rb-form-file>
<rb-form-file name="spectrum-upload" label="spectrum file" maxSize="10000000" class="space-below" multiple
(ngModelChange)="fileToArray($event)" placeholder="Select file or drag and drop" dragDrop ngModel>
</rb-form-file>
<rb-loading-spinner *ngIf="loading; else predictButton"></rb-loading-spinner>
<ng-template #predictButton>
<rb-icon-button icon="forward-right" mode="primary" *ngIf="spectrumNames.length; else placeholder"
(click)="loadPrediction()">
Predict
</rb-icon-button>
<ng-template #placeholder><div></div></ng-template>
</ng-template>
<rb-loading-spinner *ngIf="loading; else predictButton"></rb-loading-spinner>
<ng-template #predictButton>
<rb-icon-button icon="forward-right" mode="primary" *ngIf="spectrumNames.length; else placeholder"
(click)="loadPrediction()">
Predict
</rb-icon-button>
<ng-template #placeholder><div></div></ng-template>
</ng-template>
<div>
Prediction of:
<rb-form-radio name="multiple-samples" label="Single sample" [(ngModel)]="multipleSamples" [value]="false">
</rb-form-radio>
<rb-form-radio name="multiple-samples" label="Multiple samples" [(ngModel)]="multipleSamples" [value]="true">
</rb-form-radio>
</div>
<div>
Prediction of:
<rb-form-radio name="multiple-samples" label="Single sample" [(ngModel)]="multipleSamples" [value]="false">
</rb-form-radio>
<rb-form-radio name="multiple-samples" label="Multiple samples" [(ngModel)]="multipleSamples" [value]="true">
</rb-form-radio>
</div>
</div>
<!-- CSV export -->
<rb-icon-button icon="forward-right" mode="secondary" (click)="exportCSV()" *ngIf="spectrumNames.length" style="margin-right: 0.5rem">
Export to CSV
Export to CSV
</rb-icon-button>
<!-- PDF exprot -->
<rb-icon-button icon="forward-right" mode="secondary" (click)="exportPDF()" *ngIf="spectrumNames.length">
Export to PDF
Export to PDF
</rb-icon-button>
<div class="dpt-chart space-below">
<canvas baseChart
class="dpt-chart"
[datasets]="chart"
[labels]="[]"
[options]="chartOptions"
[legend]="false"
chartType="scatter">
</canvas>
<canvas baseChart
class="dpt-chart"
[datasets]="chart"
[labels]="[]"
[options]="chartOptions"
[legend]="false"
chartType="scatter">
</canvas>
</div>
<div class="shaded-container" id="disclaimer">
<h4><sup>#</sup>Disclaimer: This tool is still under development</h4>
<p>
The prediction and classification of material parameters are validated only for certain conditions.
These results may therefore under no circumstances be used to evaluate quality-relevant issues. <br>
For more details please contact <a [href]="'mailto:' + d.contact.mail">{{d.contact.name}}</a>.
</p>
<h4><sup>#</sup>Disclaimer: This tool is still under development</h4>
<p>
The prediction and classification of material parameters are validated only for certain conditions.
These results may therefore under no circumstances be used to evaluate quality-relevant issues. <br>
For more details please contact <a [href]="'mailto:' + d.contact.mail">{{d.contact.name}}</a>.
</p>
</div>

View File

@ -1,20 +1,20 @@
.dpt-chart {
max-width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 0.5rem;
max-width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 0.5rem;
}
.file-input {
display: grid;
grid-template-columns: 1fr auto;
grid-column-gap: 1rem;
display: grid;
grid-template-columns: 1fr auto;
grid-column-gap: 1rem;
}
.result {
margin: 30px 0;
margin: 30px 0;
h4 {
margin-bottom: 1rem;
}
h4 {
margin-bottom: 1rem;
}
}

View File

@ -15,267 +15,266 @@ import * as pdfFonts from 'pdfmake/build/vfs_fonts';
@Component({
selector: 'app-prediction',
templateUrl: './prediction.component.html',
styleUrls: ['./prediction.component.scss'],
animations: [
trigger(
'inOut', [
transition(':enter', [
style({height: 0, opacity: 0}),
animate('0.5s ease-out', style({height: '*', opacity: 1}))
]),
transition(':leave', [
style({height: '*', opacity: 1}),
animate('0.5s ease-in', style({height: 0, opacity: 0}))
])
]
)
]
selector: 'app-prediction',
templateUrl: './prediction.component.html',
styleUrls: ['./prediction.component.scss'],
animations: [
trigger(
'inOut', [
transition(':enter', [
style({height: 0, opacity: 0}),
animate('0.5s ease-out', style({height: '*', opacity: 1}))
]),
transition(':leave', [
style({height: '*', opacity: 1}),
animate('0.5s ease-in', style({height: 0, opacity: 0}))
])
]
)
]
})
export class PredictionComponent implements OnInit {
result: { predictions: any[], mean: any[] }; // Prediction result from python container
loading = false;
activeGroup: ModelItemModel = new ModelItemModel();
activeModelIndex = 0;
// If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given
multipleSamples = false;
spectrumNames: string[] = [];
spectrum: string[][] = [[]];
flattenedSpectra = [];
chart = [];
readonly chartInit = {
data: [],
label: 'Spectrum',
showLine: true,
fill: false,
pointRadius: 0,
borderColor: '#00a8b0',
borderWidth: 2
};
readonly chartOptions: ChartOptions = {
scales: {
xAxes: [{ticks: {min: 400, max: 4000, stepSize: 400, reverse: true}}],
yAxes: [{ticks: {}}]
},
responsive: true,
tooltips: {enabled: false},
hover: {mode: null},
maintainAspectRatio: true,
plugins: {datalabels: {display: false}}
};
result: { predictions: any[], mean: any[] }; // Prediction result from python container
loading = false;
activeGroup: ModelItemModel = new ModelItemModel();
activeModelIndex = 0;
// If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given
multipleSamples = false;
spectrumNames: string[] = [];
spectrum: string[][] = [[]];
flattenedSpectra = [];
chart = [];
readonly chartInit = {
data: [],
label: 'Spectrum',
showLine: true,
fill: false,
pointRadius: 0,
borderColor: '#00a8b0',
borderWidth: 2
};
readonly chartOptions: ChartOptions = {
scales: {
xAxes: [{ticks: {min: 400, max: 4000, stepSize: 400, reverse: true}}],
yAxes: [{ticks: {}}]
},
responsive: true,
tooltips: {enabled: false},
hover: {mode: null},
maintainAspectRatio: true,
plugins: {datalabels: {display: false}}
};
constructor(
private api: ApiService,
public d: DataService,
public login: LoginService
) {
this.chart[0] = cloneDeep(this.chartInit);
}
constructor(
private api: ApiService,
public d: DataService,
public login: LoginService
) {
this.chart[0] = cloneDeep(this.chartInit);
}
ngOnInit(): void {
this.d.load('modelGroups', () => {
this.activeGroup = this.d.arr.modelGroups[0];
});
}
ngOnInit(): void {
this.d.load('modelGroups', () => {
this.activeGroup = this.d.arr.modelGroups[0];
});
}
fileToArray(files) {
this.loading = true;
this.flattenedSpectra = [];
this.chart = [];
let load = files.length;
this.spectrumNames = files.map(e => e.name);
for (const i in files) {
if (files.hasOwnProperty(i)) {
const fileReader = new FileReader();
fileReader.onload = () => {
// Parse to database spectrum representation
this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el)))
.filter(el => el.length === 2) as any;
// Flatten to format needed for prediction
this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])};
// Add to chart
this.chart[i] = cloneDeep(this.chartInit);
this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])}));
load --;
if (load <= 0) { // All loaded
this.loadPrediction();
}
};
fileReader.readAsText(files[i]);
}
}
}
fileToArray(files) {
this.loading = true;
this.flattenedSpectra = [];
this.chart = [];
let load = files.length;
this.spectrumNames = files.map(e => e.name);
for (const i in files) {
if (files.hasOwnProperty(i)) {
const fileReader = new FileReader();
fileReader.onload = () => {
// Parse to database spectrum representation
this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el)))
.filter(el => el.length === 2) as any;
// Flatten to format needed for prediction
this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])};
// Add to chart
this.chart[i] = cloneDeep(this.chartInit);
this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])}));
load --;
if (load <= 0) { // All loaded
this.loadPrediction();
}
};
fileReader.readAsText(files[i]);
}
}
}
loadPrediction() {
this.loading = true;
this.api.post<any>(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => {
let tmp = Object.entries(omit(data, ['mean', 'std', 'label'])) // Form: [[label, [{value, color}]]]
.map((entry: any) => entry[1].map(e => ({category: entry[0], label: data.label[entry[0]], value: e.value, color: e.color}))); // Form: [[{category, label, value, color}]]
this.result = {
predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])), // Transpose tmp
mean: Object.entries(data.mean)
.map((entry:any) => ({category: entry[0], label: data.label[entry[0]], value: entry[1].value, color: entry[1].color, std: data.std[entry[0]]})) // Form: [{category, label, value, color}]
};
this.loading = false;
});
}
loadPrediction() {
this.loading = true;
this.api.post<any>(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => {
let tmp = Object.entries(omit(data, ['mean', 'std', 'label'])) // Form: [[label, [{value, color}]]]
.map((entry: any) => entry[1].map(e => ({category: entry[0], label: data.label[entry[0]], value: e.value, color: e.color}))); // Form: [[{category, label, value, color}]]
this.result = {
predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])), // Transpose tmp
mean: Object.entries(data.mean)
.map((entry:any) => ({category: entry[0], label: data.label[entry[0]], value: entry[1].value, color: entry[1].color, std: data.std[entry[0]]})) // Form: [{category, label, value, color}]
};
this.loading = false;
});
}
groupChange(index) { // Group was changed
this.activeGroup = this.d.arr.modelGroups[index];
this.activeModelIndex = 0;
this.result = undefined;
}
groupChange(index) { // Group was changed
this.activeGroup = this.d.arr.modelGroups[index];
this.activeModelIndex = 0;
this.result = undefined;
}
// Aggregates spectrum names and prediction values into an associative array
prepareExport() {
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
const values = this.result.predictions
.map(prediction => prediction
.filter(field => field.category === 'Prediction')
.map(field => field.value));
return zip(this.spectrumNames, values);
}
// Aggregates spectrum names and prediction values into an associative array
prepareExport() {
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
const values = this.result.predictions
.map(prediction => prediction
.filter(field => field.category === 'Prediction')
.map(field => field.value));
return zip(this.spectrumNames, values);
}
// Generates a timestamp suitable for file naming
generateTimestamp() {
let d = new Date();
return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '--' + d.getHours() + "-" + d.getMinutes();
}
// Generates a timestamp suitable for file naming
generateTimestamp() {
let d = new Date();
return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '--' + d.getHours() + "-" + d.getMinutes();
}
// Converts the prediction results to a CSV file
exportCSV() {
const predictions = this.prepareExport();
const csv = predictions.map(line => line.join(";")).join("\n");
FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'predictions-' + this.generateTimestamp() + '.csv');
}
// Converts the prediction results to a CSV file
exportCSV() {
const predictions = this.prepareExport();
const csv = predictions.map(line => line.join(";")).join("\n");
FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'predictions-' + this.generateTimestamp() + '.csv');
}
// Converts the prediction results to a PDF file
exportPDF() {
const doc = {
content: [
{
text: 'DeFinMa - Decoding the Fingerprint of Materials by AI',
style: 'header'
},
{
table: {
widths: ['auto', '*', 'auto', 'auto', 'auto'],
body: [
[
{text: new Date().toLocaleDateString(), style: 'tableHeader'},
{text: 'Customer', style: 'tableHeader'},
{text: 'Security class', style: 'tableHeader'},
{text: 'Person in charge', style: 'tableHeader'},
{text: 'Phone', style: 'tableHeader'}],
[
this.activeGroup.group,
this.login.username,
'Intern',
{
stack: [
'CR/APS1-Lotter',
'CR/APS1-Lingenfelser'
]
},
{
stack: [
'0711/811-49017',
'0711/811-6897'
]
}
]
]
}
},
{
text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*',
style: 'subheader'
},
{
text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : ''))
},
{
table: {
widths: ['*', 'auto'],
body: [
[{text: 'Input Data / Sample Name', style: 'tableHeader'}, {text: 'Prediction*', style: 'tableHeader'}]
].concat(this.prepareExport())
}
},
{
text: 'Reference Data',
style: 'subheader'
},
{
image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'),
width: 500
},
{
table: {
body: [[{
stack: [
{
text: '*Disclaimer: This tool is still under development and Testing',
style: 'subsubheader'
},
{
text: [
'The prediction and classification of material parameters are validated only for certain conditions.',
'These results may therefore under no circumstances be used to evaluate quality-relevant issues.',
'For more details please contact ',
{
text: 'CR/APS1-Lingenfelser',
link: 'mailto:dominic.lingenfelser@bosch.com'
},
'.'
]
},
]
}]],
},
margin: [25, 20]
},
{
table: {
widths: ['*', '*', 'auto'],
body: [
[{text: 'Pr\u00fcfung', style: 'tableHeader'}, {text: 'Freigabe', style: 'tableHeader'}, {text: 'Datum', style: 'tableHeader'}],
['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()]
],
}
}
],
footer: {
text: '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.',
fontSize: 8,
alignment: 'center'
},
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 10]
},
subheader: {
fontSize: 15,
bold: true,
margin: [0, 8]
},
subsubheader: {
bold: true,
margin: [0, 5]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
}
},
pageMargins: [50, 50, 50, 15]
};
// Converts the prediction results to a PDF file
exportPDF() {
const doc = {
content: [
{
text: 'DeFinMa - Decoding the Fingerprint of Materials by AI',
style: 'header'
},
{
table: {
widths: ['auto', '*', 'auto', 'auto', 'auto'],
body: [[
{text: new Date().toLocaleDateString(), style: 'tableHeader'},
{text: 'Customer', style: 'tableHeader'},
{text: 'Security class', style: 'tableHeader'},
{text: 'Person in charge', style: 'tableHeader'},
{text: 'Phone', style: 'tableHeader'}],
[
this.activeGroup.group,
this.login.username,
'Intern',
{
stack: [
'CR/APS1-Lotter',
'CR/APS1-Lingenfelser'
]
},
{
stack: [
'0711/811-49017',
'0711/811-6897'
]
}
]
]
}
},
{
text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*',
style: 'subheader'
},
{
text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : ''))
},
{
table: {
widths: ['*', 'auto'],
body: [
[{text: 'Input Data / Sample Name', style: 'tableHeader'}, {text: 'Prediction*', style: 'tableHeader'}]
].concat(this.prepareExport())
}
},
{
text: 'Reference Data',
style: 'subheader'
},
{
image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'),
width: 500
},
{
table: {
body: [[{
stack: [
{
text: '*Disclaimer: This tool is still under development and Testing',
style: 'subsubheader'
},
{
text: [
'The prediction and classification of material parameters are validated only for certain conditions.',
'These results may therefore under no circumstances be used to evaluate quality-relevant issues.',
'For more details please contact ',
{
text: 'CR/APS1-Lingenfelser',
link: 'mailto:dominic.lingenfelser@bosch.com'
},
'.'
]
},
]
}]],
},
margin: [25, 20]
},
{
table: {
widths: ['*', '*', 'auto'],
body: [
[{text: 'Pr\u00fcfung', style: 'tableHeader'}, {text: 'Freigabe', style: 'tableHeader'}, {text: 'Datum', style: 'tableHeader'}],
['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()]
],
}
}
],
footer: {
text: '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.',
fontSize: 8,
alignment: 'center'
},
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 10]
},
subheader: {
fontSize: 15,
bold: true,
margin: [0, 8]
},
subsubheader: {
bold: true,
margin: [0, 5]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
}
},
pageMargins: [50, 50, 50, 15]
};
pdfMake.createPdf(doc).download('predictions-' + this.generateTimestamp() + '.pdf');
}
pdfMake.createPdf(doc).download('predictions-' + this.generateTimestamp() + '.pdf');
}
}

View File

@ -2,25 +2,25 @@ import { Injectable } from '@angular/core';
import {Observable, Subject} from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class ArrayInputHelperService {
com: Subject<{ id: string, index: number, value: any }> = new Subject();
com: Subject<{ id: string, index: number, value: any }> = new Subject();
constructor() { }
constructor() { }
values(id: string) { // Observable which returns new values as they come for subscribed id
return new Observable<{index: number, value: any}>(observer => {
this.com.subscribe(data => {
if (data.id === id) {
observer.next({index: data.index, value: data.value});
}
});
});
}
values(id: string) { // Observable which returns new values as they come for subscribed id
return new Observable<{index: number, value: any}>(observer => {
this.com.subscribe(data => {
if (data.id === id) {
observer.next({index: data.index, value: data.value});
}
});
});
}
newValue(id: string, index: number, value: any) { // Set new value
this.com.next({id, index, value});
}
newValue(id: string, index: number, value: any) { // Set new value
this.com.next({id, index, value});
}
}

View File

@ -1,3 +1,3 @@
<ng-container *ngFor="let ignore of [].constructor(values.length); index as i">
<ng-container *ngTemplateOutlet="item.templateRef; context: {$implicit: {i: i, value: values[i]}}"></ng-container>
<ng-container *ngTemplateOutlet="item.templateRef; context: {$implicit: {i: i, value: values[i]}}"></ng-container>
</ng-container>

View File

@ -1,13 +1,13 @@
import {
AfterViewInit,
Component,
ContentChild,
Directive,
forwardRef,
HostListener,
Input,
OnInit,
TemplateRef
AfterViewInit,
Component,
ContentChild,
Directive,
forwardRef,
HostListener,
Input,
OnInit,
TemplateRef
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import cloneDeep from 'lodash/cloneDeep';
@ -15,150 +15,150 @@ import {ArrayInputHelperService} from './array-input-helper.service';
@Directive({ // Directive for template and input values
// tslint:disable-next-line:directive-selector
selector: '[rbArrayInputItem]'
// tslint:disable-next-line:directive-selector
selector: '[rbArrayInputItem]'
})
export class RbArrayInputItemDirective {
constructor(public templateRef: TemplateRef<any>) {
}
constructor(public templateRef: TemplateRef<any>) {
}
}
@Directive({ // Directive for change detection
// tslint:disable-next-line:directive-selector
selector: '[rbArrayInputListener]'
// tslint:disable-next-line:directive-selector
selector: '[rbArrayInputListener]'
})
export class RbArrayInputListenerDirective {
@Input() rbArrayInputListener: string;
@Input() index;
@Input() rbArrayInputListener: string;
@Input() index;
constructor(
private helperService: ArrayInputHelperService
) { }
constructor(
private helperService: ArrayInputHelperService
) { }
@HostListener('ngModelChange', ['$event'])
onChange(event) { // Emit new value
this.helperService.newValue(this.rbArrayInputListener, this.index, event);
}
@HostListener('ngModelChange', ['$event'])
onChange(event) { // Emit new value
this.helperService.newValue(this.rbArrayInputListener, this.index, event);
}
}
@Component({
// tslint:disable-next-line:component-selector
selector: 'rb-array-input',
templateUrl: './rb-array-input.component.html',
styleUrls: ['./rb-array-input.component.scss'],
providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RbArrayInputComponent), multi: true}]
// tslint:disable-next-line:component-selector
selector: 'rb-array-input',
templateUrl: './rb-array-input.component.html',
styleUrls: ['./rb-array-input.component.scss'],
providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RbArrayInputComponent), multi: true}]
})
export class RbArrayInputComponent implements ControlValueAccessor, OnInit, AfterViewInit {
pushTemplate: any = ''; // Array element template
@Input('pushTemplate') set _pushTemplate(value) {
this.pushTemplate = value;
if (this.values.length) {
this.updateArray();
}
}
@Input() pushPath: string = null;
pushTemplate: any = ''; // Array element template
@Input('pushTemplate') set _pushTemplate(value) {
this.pushTemplate = value;
if (this.values.length) {
this.updateArray();
}
}
@Input() pushPath: string = null;
@ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective;
@ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective;
@ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective;
@ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective;
values = []; // Main array to display
values = []; // Main array to display
onChange = (ignore?: any): void => {};
onTouched = (ignore?: any): void => {};
onChange = (ignore?: any): void => {};
onTouched = (ignore?: any): void => {};
constructor(
private helperService: ArrayInputHelperService
) { }
constructor(
private helperService: ArrayInputHelperService
) { }
ngOnInit(): void {
}
ngOnInit(): void {
}
ngAfterViewInit() {
setTimeout(() => { // Needed to find reference
this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => { // Action on value change
// Assign value
if (this.pushPath) {
this.values[data.index][this.pushPath] = data.value;
}
else {
this.values[data.index] = data.value;
}
this.updateArray();
});
}, 0);
}
ngAfterViewInit() {
setTimeout(() => { // Needed to find reference
this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => { // Action on value change
// Assign value
if (this.pushPath) {
this.values[data.index][this.pushPath] = data.value;
}
else {
this.values[data.index] = data.value;
}
this.updateArray();
});
}, 0);
}
updateArray() {
let res;
// Adjust fields if pushTemplate is specified
if (this.pushTemplate !== null) {
if (this.pushPath) {
// Remove last element if last two are empty
if (this.values[this.values.length - 1][this.pushPath] === '' &&
this.values[this.values.length - 2][this.pushPath] === '') {
this.values.pop();
}
// Add element if last all are filled
else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) {
this.values.push(cloneDeep(this.pushTemplate));
}
res = this.values.filter(e => e[this.pushPath] !== '');
}
else {
// Remove last element if last two are empty
if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') {
this.values.pop();
}
else if (this.values.filter(e => e !== '').length === this.values.length) { // Add element if all are is filled
this.values.push(cloneDeep(this.pushTemplate));
}
res = this.values.filter(e => e !== '');
}
}
else {
this.values = [this.values[0]];
res = this.values;
}
if (!res.length) {
res = [''];
}
this.onChange(res); // Trigger ngModel with filled elements
}
updateArray() {
let res;
// Adjust fields if pushTemplate is specified
if (this.pushTemplate !== null) {
if (this.pushPath) {
// Remove last element if last two are empty
if (this.values[this.values.length - 1][this.pushPath] === '' &&
this.values[this.values.length - 2][this.pushPath] === '') {
this.values.pop();
}
// Add element if last all are filled
else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) {
this.values.push(cloneDeep(this.pushTemplate));
}
res = this.values.filter(e => e[this.pushPath] !== '');
}
else {
// Remove last element if last two are empty
if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') {
this.values.pop();
}
else if (this.values.filter(e => e !== '').length === this.values.length) { // Add element if all are is filled
this.values.push(cloneDeep(this.pushTemplate));
}
res = this.values.filter(e => e !== '');
}
}
else {
this.values = [this.values[0]];
res = this.values;
}
if (!res.length) {
res = [''];
}
this.onChange(res); // Trigger ngModel with filled elements
}
writeValue(obj: any) { // Add empty value on init
if (obj) {
if (this.pushTemplate !== null) {
// Filter out empty values
if (this.pushPath) {
this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)];
}
else {
this.values = [...obj.filter(e => e !== ''), cloneDeep(this.pushTemplate)];
}
}
else {
this.values = obj;
}
}
else {
if (this.pushTemplate !== null) {
this.values = [cloneDeep(this.pushTemplate)];
}
else {
this.values = [''];
}
}
}
writeValue(obj: any) { // Add empty value on init
if (obj) {
if (this.pushTemplate !== null) {
// Filter out empty values
if (this.pushPath) {
this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)];
}
else {
this.values = [...obj.filter(e => e !== ''), cloneDeep(this.pushTemplate)];
}
}
else {
this.values = obj;
}
}
else {
if (this.pushTemplate !== null) {
this.values = [cloneDeep(this.pushTemplate)];
}
else {
this.values = [''];
}
}
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
}

View File

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RbTableComponent } from './rb-table/rb-table.component';
import {RbArrayInputComponent, RbArrayInputListenerDirective, RbArrayInputItemDirective} from
'./rb-array-input/rb-array-input.component';
'./rb-array-input/rb-array-input.component';
import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components';
import {FormsModule} from '@angular/forms';
import { RbIconButtonComponent } from './rb-icon-button/rb-icon-button.component';
@ -10,24 +10,24 @@ import { RbIconButtonComponent } from './rb-icon-button/rb-icon-button.component
@NgModule({
declarations: [
RbTableComponent,
RbArrayInputComponent,
RbArrayInputListenerDirective,
RbArrayInputItemDirective,
RbIconButtonComponent
],
imports: [
CommonModule,
FormsModule,
RbUiComponentsModule
],
exports: [
RbTableComponent,
RbArrayInputComponent,
RbArrayInputListenerDirective,
RbArrayInputItemDirective,
RbIconButtonComponent
]
declarations: [
RbTableComponent,
RbArrayInputComponent,
RbArrayInputListenerDirective,
RbArrayInputItemDirective,
RbIconButtonComponent
],
imports: [
CommonModule,
FormsModule,
RbUiComponentsModule
],
exports: [
RbTableComponent,
RbArrayInputComponent,
RbArrayInputListenerDirective,
RbArrayInputItemDirective,
RbIconButtonComponent
]
})
export class RbCustomInputsModule { }

View File

@ -1,4 +1,4 @@
<button class="rb-btn rb" [ngClass]="'rb-' + mode" [type]="type" [disabled]="disabled">
<span class="rb-ic" [ngClass]="'rb-ic-' + icon" [class.icon-space]="iconOnly === undefined"></span>
<ng-content></ng-content>
<span class="rb-ic" [ngClass]="'rb-ic-' + icon" [class.icon-space]="iconOnly === undefined"></span>
<ng-content></ng-content>
</button>

View File

@ -1,8 +1,8 @@
.icon-space {
margin-right: 0.1em !important;
margin-right: 0.1em !important;
}
button.rb-btn > span:not(.icon-space) {
margin-right: -10px;
margin-left: -10px;
margin-right: -10px;
margin-left: -10px;
}

View File

@ -2,22 +2,22 @@ import {Component, Input, OnInit} from '@angular/core';
@Component({
// tslint:disable-next-line:component-selector
selector: 'rb-icon-button',
templateUrl: './rb-icon-button.component.html',
styleUrls: ['./rb-icon-button.component.scss']
// tslint:disable-next-line:component-selector
selector: 'rb-icon-button',
templateUrl: './rb-icon-button.component.html',
styleUrls: ['./rb-icon-button.component.scss']
})
export class RbIconButtonComponent implements OnInit {
@Input() icon: string;
@Input() mode: string;
@Input() iconOnly;
@Input() disabled;
@Input() type = 'button';
@Input() icon: string;
@Input() mode: string;
@Input() iconOnly;
@Input() disabled;
@Input() type = 'button';
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -1,5 +1,5 @@
<div class="table-wrapper space-below" [class.scroll-top]="scrollTop !== undefined">
<table [class.ellipsis]="ellipsis !== undefined">
<ng-content></ng-content>
</table>
<table [class.ellipsis]="ellipsis !== undefined">
<ng-content></ng-content>
</table>
</div>

View File

@ -1,38 +1,38 @@
@import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors";
.table-wrapper.scroll-top {
overflow-x: auto;
width: 100%;
overflow-x: auto;
width: 100%;
&, & > table { // scrollbar at the top
transform:rotateX(180deg);
-ms-transform:rotateX(180deg); /* IE 9 */
-webkit-transform:rotateX(180deg); /* Safari and Chrome */
}
&, & > table { // scrollbar at the top
transform:rotateX(180deg);
-ms-transform:rotateX(180deg); /* IE 9 */
-webkit-transform:rotateX(180deg); /* Safari and Chrome */
}
}
table {
width: 100%;
border-collapse: collapse;
width: 100%;
border-collapse: collapse;
::ng-deep tr {
border-bottom: 1px solid $color-gray-mercury;
::ng-deep tr {
border-bottom: 1px solid $color-gray-mercury;
::ng-deep td, ::ng-deep th {
padding: 8px 5px;
}
::ng-deep td, ::ng-deep th {
padding: 8px 5px;
}
::ng-deep th {
text-align: left;
}
}
::ng-deep th {
text-align: left;
}
}
}
table.ellipsis {
::ng-deep td, ::ng-deep th {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
}
::ng-deep td, ::ng-deep th {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
}
}

View File

@ -1,19 +1,19 @@
import {Component, Input, OnInit} from '@angular/core';
@Component({
// tslint:disable-next-line:component-selector
selector: 'rb-table',
templateUrl: './rb-table.component.html',
styleUrls: ['./rb-table.component.scss']
// tslint:disable-next-line:component-selector
selector: 'rb-table',
templateUrl: './rb-table.component.html',
styleUrls: ['./rb-table.component.scss']
})
export class RbTableComponent implements OnInit {
@Input() scrollTop;
@Input() ellipsis;
@Input() scrollTop;
@Input() ellipsis;
constructor() { }
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {
}
}

View File

@ -4,335 +4,335 @@
<ng-template #content>
<!--BASE-->
<!--BASE-->
<form #sampleForm="ngForm" *ngIf="view.base">
<div class="sample">
<div>
<rb-form-input name="materialname" label="product name" [rbDebounceTime]="0" [rbInitialOpen]="true"
[rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" appValidate="stringOf"
(keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" ngModel
[appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true"
*ngIf="mode === 'new' || (baseSample.material && baseSample.material.name !== undefined)"
title="trade name of the material, eg. Ultradur B4300 G6">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
<ng-template rbFormValidationMessage="failure">Unknown material, add properties for new material</ng-template>
</rb-form-input>
<rb-icon-button class="set-new-material space-below" icon="add" mode="secondary"
(click)="setNewMaterial(!newMaterial)"
*ngIf="mode === 'new' || (baseSample.material && baseSample.material.name !== undefined)">
New material
</rb-icon-button>
</div>
<form #sampleForm="ngForm" *ngIf="view.base">
<div class="sample">
<div>
<rb-form-input name="materialname" label="product name" [rbDebounceTime]="0" [rbInitialOpen]="true"
[rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" appValidate="stringOf"
(keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" ngModel
[appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true"
*ngIf="mode === 'new' || (baseSample.material && baseSample.material.name !== undefined)"
title="trade name of the material, eg. Ultradur B4300 G6">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
<ng-template rbFormValidationMessage="failure">Unknown material, add properties for new material</ng-template>
</rb-form-input>
<rb-icon-button class="set-new-material space-below" icon="add" mode="secondary"
(click)="setNewMaterial(!newMaterial)"
*ngIf="mode === 'new' || (baseSample.material && baseSample.material.name !== undefined)">
New material
</rb-icon-button>
</div>
<div class="material shaded-container" *ngIf="newMaterial" [@inOut]>
<h4>Material properties</h4>
<rb-form-input name="supplier" label="supplier"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialSuppliers)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.supplier" #supplierInput="ngModel"
(focusout)="checkTypo($event, 'materialSuppliers', 'supplier', modalWarning)"
title="material supplier, eg. BASF">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="group" label="group"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialGroups)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.group" #groupInput="ngModel"
(focusout)="checkTypo($event, 'materialGroups', 'group', modalWarning)"
title="chemical material type, eg. PA66">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
</rb-form-input>
<ng-template #modalWarning>
<rb-alert alertTitle="Warning" type="warning" okBtnLabel="Use suggestion" cancelBtnLabel="Keep value">
The specified {{modalText.list}} could not be found in the list. <br>
Did you mean {{modalText.suggestion}}?
</rb-alert>
</ng-template>
<rb-array-input [(ngModel)]="material.numbers" name="materialNumbers" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" [rbArrayInputListener]="'materialNumber'" [index]="item.i"
label="material number" appValidate="string" [name]="'materialNumber-' + item.i"
[ngModel]="item.value" title="Bosch material part number, eg. 5515753021"></rb-form-input>
</rb-array-input>
<rb-form-select name="propertiesSelect" label="Type" title="=overall material group specific properties"
[(ngModel)]="material.properties.material_template">
<option *ngFor="let m of d.latest.materialTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<rb-form-input *ngFor="let parameter of
d.id.materialTemplates[material.properties.material_template].parameters;
index as i" [name]="'materialParameter' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="material.properties[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</div>
<div class="material shaded-container" *ngIf="newMaterial" [@inOut]>
<h4>Material properties</h4>
<rb-form-input name="supplier" label="supplier"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialSuppliers)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.supplier" #supplierInput="ngModel"
(focusout)="checkTypo($event, 'materialSuppliers', 'supplier', modalWarning)"
title="material supplier, eg. BASF">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-input name="group" label="group"
[rbFormInputAutocomplete]="autocomplete.bind(this, d.arr.materialGroups)"
[rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required
[(ngModel)]="material.group" #groupInput="ngModel"
(focusout)="checkTypo($event, 'materialGroups', 'group', modalWarning)"
title="chemical material type, eg. PA66">
<ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
</rb-form-input>
<ng-template #modalWarning>
<rb-alert alertTitle="Warning" type="warning" okBtnLabel="Use suggestion" cancelBtnLabel="Keep value">
The specified {{modalText.list}} could not be found in the list. <br>
Did you mean {{modalText.suggestion}}?
</rb-alert>
</ng-template>
<rb-array-input [(ngModel)]="material.numbers" name="materialNumbers" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" [rbArrayInputListener]="'materialNumber'" [index]="item.i"
label="material number" appValidate="string" [name]="'materialNumber-' + item.i"
[ngModel]="item.value" title="Bosch material part number, eg. 5515753021"></rb-form-input>
</rb-array-input>
<rb-form-select name="propertiesSelect" label="Type" title="=overall material group specific properties"
[(ngModel)]="material.properties.material_template">
<option *ngFor="let m of d.latest.materialTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<rb-form-input *ngFor="let parameter of
d.id.materialTemplates[material.properties.material_template].parameters;
index as i" [name]="'materialParameter' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="material.properties[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</div>
<div>
<rb-form-select name="type" label="type" required [(ngModel)]="baseSample.type" ngModel
*ngIf="baseSample.type !== undefined"
title="material status of the sample">
<option value="as-delivered/raw">as-delivered/raw</option>
<option value="processed">processed</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input name="color" label="color" appValidate="string" [(ngModel)]="baseSample.color"
*ngIf="baseSample.color !== undefined" #colorInput="ngModel" title="sample color, eg. black">
<ng-template rbFormValidationMessage="failure">{{colorInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-form-input name="batch" label="batch" appValidate="string" [(ngModel)]="baseSample.batch"
#batchInput="ngModel" *ngIf="baseSample.batch !== undefined"
title="batch number the sample was from, eg. 2264486614">
<ng-template rbFormValidationMessage="failure">{{batchInput.errors.failure}}</ng-template>
</rb-form-input>
</div>
</div>
<div>
<rb-form-select name="type" label="type" required [(ngModel)]="baseSample.type" ngModel
*ngIf="baseSample.type !== undefined"
title="material status of the sample">
<option value="as-delivered/raw">as-delivered/raw</option>
<option value="processed">processed</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input name="color" label="color" appValidate="string" [(ngModel)]="baseSample.color"
*ngIf="baseSample.color !== undefined" #colorInput="ngModel" title="sample color, eg. black">
<ng-template rbFormValidationMessage="failure">{{colorInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
<rb-form-input name="batch" label="batch" appValidate="string" [(ngModel)]="baseSample.batch"
#batchInput="ngModel" *ngIf="baseSample.batch !== undefined"
title="batch number the sample was from, eg. 2264486614">
<ng-template rbFormValidationMessage="failure">{{batchInput.errors.failure}}</ng-template>
</rb-form-input>
</div>
</div>
<div class="notes" *ngIf="baseSample.notes !== undefined">
<rb-form-input name="comment" label="comment" appValidate="stringLength" [appValidateArgs]="[512]"
[(ngModel)]="baseSample.notes.comment" #commentInput="ngModel"
title="general remarks that cannot be expressed in additional properties, eg. stabilized">
<ng-template rbFormValidationMessage="failure">{{commentInput.errors.failure}}</ng-template>
</rb-form-input>
<h5>Sample references</h5>
<div *ngFor="let reference of sampleReferences; index as i" class="two-col" [@inOut]>
<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]" ngModel
title="sample number of the referenced sample, eg. An31">
<ng-template rbFormValidationMessage="failure">Unknown sample number</ng-template>
</rb-form-input>
</div>
<rb-form-input [name]="'sr-relation' + i" label="relation" appValidate="string" [required]="reference[0] !== ''"
[(ngModel)]="reference[1]" title="description how the samples are connected, eg. belongs to">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</div>
<h5>Additional properties</h5>
<rb-array-input [(ngModel)]="customFields" name="customFields" [pushTemplate]="['', '']" pushPath="0"
class="two-col" [@inOut]>
<ng-container *rbArrayInputItem="let item">
<div>
<rb-form-input [name]="'cf-key' + item.i" label="key" [rbArrayInputListener]="'cf-key'" [index]="item.i"
[rbFormInputAutocomplete]="autocomplete.bind(this, availableCustomFields)"
[rbDebounceTime]="0"
[rbInitialOpen]="true" appValidate="unique" [appValidateArgs]="[uniqueCfValues(item.i)]"
[(ngModel)]="item.value[0]" #keyInput="ngModel" title="name of additional property, eg. vwz">
<ng-template rbFormValidationMessage="failure">{{keyInput.errors.failure}}</ng-template>
</rb-form-input>
</div>
<rb-form-input [name]="'cf-value' + item.i" label="value" appValidate="string"
[required]="item.value[0] !== ''"
[(ngModel)]="item.value[1]" title="value of additional property, eg. 0 min">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
</rb-array-input>
</div>
<div class="notes" *ngIf="baseSample.notes !== undefined">
<rb-form-input name="comment" label="comment" appValidate="stringLength" [appValidateArgs]="[512]"
[(ngModel)]="baseSample.notes.comment" #commentInput="ngModel"
title="general remarks that cannot be expressed in additional properties, eg. stabilized">
<ng-template rbFormValidationMessage="failure">{{commentInput.errors.failure}}</ng-template>
</rb-form-input>
<h5>Sample references</h5>
<div *ngFor="let reference of sampleReferences; index as i" class="two-col" [@inOut]>
<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]" ngModel
title="sample number of the referenced sample, eg. An31">
<ng-template rbFormValidationMessage="failure">Unknown sample number</ng-template>
</rb-form-input>
</div>
<rb-form-input [name]="'sr-relation' + i" label="relation" appValidate="string" [required]="reference[0] !== ''"
[(ngModel)]="reference[1]" title="description how the samples are connected, eg. belongs to">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</div>
<h5>Additional properties</h5>
<rb-array-input [(ngModel)]="customFields" name="customFields" [pushTemplate]="['', '']" pushPath="0"
class="two-col" [@inOut]>
<ng-container *rbArrayInputItem="let item">
<div>
<rb-form-input [name]="'cf-key' + item.i" label="key" [rbArrayInputListener]="'cf-key'" [index]="item.i"
[rbFormInputAutocomplete]="autocomplete.bind(this, availableCustomFields)"
[rbDebounceTime]="0"
[rbInitialOpen]="true" appValidate="unique" [appValidateArgs]="[uniqueCfValues(item.i)]"
[(ngModel)]="item.value[0]" #keyInput="ngModel" title="name of additional property, eg. vwz">
<ng-template rbFormValidationMessage="failure">{{keyInput.errors.failure}}</ng-template>
</rb-form-input>
</div>
<rb-form-input [name]="'cf-value' + item.i" label="value" appValidate="string"
[required]="item.value[0] !== ''"
[(ngModel)]="item.value[1]" title="value of additional property, eg. 0 min">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
</rb-array-input>
</div>
<div *ngIf="samples.length; else generateSamples" class="space-below">
<rb-icon-button icon="save" mode="primary" type="submit" (click)="saveSample()"
[disabled]="!sampleForm.form.valid">
Save sample
</rb-icon-button>
<rb-icon-button class="delete-sample" icon="delete" mode="danger" *ngIf="samples.length > 1"
(click)="deleteConfirm(modalDeleteConfirm)">
Delete samples
</rb-icon-button>
</div>
<ng-template #generateSamples>
<rb-form-input type="number" name="sample-count" label="number of samples" pattern="^\d+?$" required
rbNumberConverter rbMin="1" [(ngModel)]="sampleCount" class="sample-count"
title="number of samples to create with this base information">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
<ng-template rbFormValidationMessage="rbMin">Must be at least 1</ng-template>
</rb-form-input>
<button class="rb-btn rb-primary space-below" type="submit" (click)="saveSample()"
[disabled]="!sampleForm.form.valid">
Generate sample{{sampleCount > 1 ? 's' : ''}}
</button>
</ng-template>
</form>
<div *ngIf="samples.length; else generateSamples" class="space-below">
<rb-icon-button icon="save" mode="primary" type="submit" (click)="saveSample()"
[disabled]="!sampleForm.form.valid">
Save sample
</rb-icon-button>
<rb-icon-button class="delete-sample" icon="delete" mode="danger" *ngIf="samples.length > 1"
(click)="deleteConfirm(modalDeleteConfirm)">
Delete samples
</rb-icon-button>
</div>
<ng-template #generateSamples>
<rb-form-input type="number" name="sample-count" label="number of samples" pattern="^\d+?$" required
rbNumberConverter rbMin="1" [(ngModel)]="sampleCount" class="sample-count"
title="number of samples to create with this base information">
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
<ng-template rbFormValidationMessage="rbMin">Must be at least 1</ng-template>
</rb-form-input>
<button class="rb-btn rb-primary space-below" type="submit" (click)="saveSample()"
[disabled]="!sampleForm.form.valid">
Generate sample{{sampleCount > 1 ? 's' : ''}}
</button>
</ng-template>
</form>
<!--BASE SUMMARY-->
<!--BASE SUMMARY-->
<div *ngIf="view.baseSum">
<h3 *ngIf="mode === 'new'">Successfully added samples:</h3>
<span class="rb-ic rb-ic-edit clickable" (click)="checkFormAfterInit = view.base = true; view.baseSum = false">
</span>
<rb-table id="response-data">
<tr><td>Sample Number</td><td>{{baseSample.number}}</td></tr>
<tr><td>Material</td><td>{{material.name}}</td></tr>
<tr><td>Type</td><td>{{baseSample.type}}</td></tr>
<tr><td>Color</td><td>{{baseSample.color}}</td></tr>
<tr><td>Batch</td><td>{{baseSample.batch}}</td></tr>
<tr><td>Comment</td><td>{{baseSample.notes | exists:'comment'}}</td></tr>
<tr *ngFor="let reference of sampleReferences.slice(0, -1)">
<td>Sample reference</td><td>{{reference[0]}} - {{reference[1]}}</td>
</tr>
<tr *ngFor="let field of customFields"><td>{{field[0]}}</td><td>{{field[1]}}</td></tr>
</rb-table>
</div>
<div *ngIf="view.baseSum">
<h3 *ngIf="mode === 'new'">Successfully added samples:</h3>
<span class="rb-ic rb-ic-edit clickable" (click)="checkFormAfterInit = view.base = true; view.baseSum = false">
</span>
<rb-table id="response-data">
<tr><td>Sample Number</td><td>{{baseSample.number}}</td></tr>
<tr><td>Material</td><td>{{material.name}}</td></tr>
<tr><td>Type</td><td>{{baseSample.type}}</td></tr>
<tr><td>Color</td><td>{{baseSample.color}}</td></tr>
<tr><td>Batch</td><td>{{baseSample.batch}}</td></tr>
<tr><td>Comment</td><td>{{baseSample.notes | exists:'comment'}}</td></tr>
<tr *ngFor="let reference of sampleReferences.slice(0, -1)">
<td>Sample reference</td><td>{{reference[0]}} - {{reference[1]}}</td>
</tr>
<tr *ngFor="let field of customFields"><td>{{field[0]}}</td><td>{{field[1]}}</td></tr>
</rb-table>
</div>
<!--CM-->
<!--CM-->
<div *ngIf="view.cm">
<form #cmForm="ngForm" [ngClass]="{'cm-form': samples.length > 1}">
<rb-tab-panel (tabChanged)="cmSampleIndex = $event" class="space-below" *ngIf="samples.length > 1">
<ng-container *ngFor="let sample of samples; index as i">
<div *rbTabPanelItem="sample.number; id: i"></div>
</ng-container>
</rb-tab-panel>
<div *ngFor="let sample of samples; index as gIndex"
[ngStyle]="{display: gIndex == cmSampleIndex || gIndex == cmSampleIndex + 1 ? 'initial' : 'none'}">
<h4 *ngIf="samples.length > 1">{{sample.number}}</h4>
<div class="conditions shaded-container space-below">
<h5>
Condition
<button class="rb-btn rb-secondary condition-set" type="button" (click)="toggleCondition(sample)">
{{sample.condition.condition_template ? 'Do not set condition' : 'Set condition'}}
</button>
</h5>
<div *ngIf="sample.condition.condition_template" [@inOut]>
<rb-form-select [name]="'conditionSelect-' + gIndex" label="Condition"
[(ngModel)]="sample.condition.condition_template"
(ngModelChange)="reValidate()">
<option [value]="sample.condition.condition_template">
{{d.id.conditionTemplates[sample.condition.condition_template].name}} - current
</option>
<option *ngFor="let c of d.latest.conditionTemplates" [value]="c._id">{{c.name}}</option>
</rb-form-select>
<div *ngIf="view.cm">
<form #cmForm="ngForm" [ngClass]="{'cm-form': samples.length > 1}">
<rb-tab-panel (tabChanged)="cmSampleIndex = $event" class="space-below" *ngIf="samples.length > 1">
<ng-container *ngFor="let sample of samples; index as i">
<div *rbTabPanelItem="sample.number; id: i"></div>
</ng-container>
</rb-tab-panel>
<div *ngFor="let sample of samples; index as gIndex"
[ngStyle]="{display: gIndex == cmSampleIndex || gIndex == cmSampleIndex + 1 ? 'initial' : 'none'}">
<h4 *ngIf="samples.length > 1">{{sample.number}}</h4>
<div class="conditions shaded-container space-below">
<h5>
Condition
<button class="rb-btn rb-secondary condition-set" type="button" (click)="toggleCondition(sample)">
{{sample.condition.condition_template ? 'Do not set condition' : 'Set condition'}}
</button>
</h5>
<div *ngIf="sample.condition.condition_template" [@inOut]>
<rb-form-select [name]="'conditionSelect-' + gIndex" label="Condition"
[(ngModel)]="sample.condition.condition_template"
(ngModelChange)="reValidate()">
<option [value]="sample.condition.condition_template">
{{d.id.conditionTemplates[sample.condition.condition_template].name}} - current
</option>
<option *ngFor="let c of d.latest.conditionTemplates" [value]="c._id">{{c.name}}</option>
</rb-form-select>
<ng-container *ngFor="let parameter of
d.id.conditionTemplates[sample.condition.condition_template].parameters; index as i"
[ngSwitch]="(parameter.range.values ? 1 : 0)">
<rb-form-select *ngSwitchCase="1"
[name]="'conditionParameter-' + gIndex + '-' + i"
[label]="parameter.name" [(ngModel)]="sample.condition[parameter.name]" ngModel>
<option *ngFor="let value of parameter.range.values" [value]="value">{{value}}</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input *ngSwitchDefault (focus)="checkFormAfterInit = true"
[name]="'conditionParameter-' + gIndex + '-' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="sample.condition[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
</div>
</div>
<ng-container *ngFor="let parameter of
d.id.conditionTemplates[sample.condition.condition_template].parameters; index as i"
[ngSwitch]="(parameter.range.values ? 1 : 0)">
<rb-form-select *ngSwitchCase="1"
[name]="'conditionParameter-' + gIndex + '-' + i"
[label]="parameter.name" [(ngModel)]="sample.condition[parameter.name]" ngModel>
<option *ngFor="let value of parameter.range.values" [value]="value">{{value}}</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input *ngSwitchDefault (focus)="checkFormAfterInit = true"
[name]="'conditionParameter-' + gIndex + '-' + i"
[label]="parameter.name" appValidate="string" required
[(ngModel)]="sample.condition[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
</div>
</div>
<div class="measurements shaded-container space-below">
<h5>
Measurements
<rb-icon-button icon="undo" mode="secondary" *ngIf="measurementRestoreData.length"
class="restore-measurements" (click)="restoreMeasurements()">
Restore measurements
</rb-icon-button>
</h5>
<div *ngFor="let measurement of sample.measurements; index as mIndex" [@inOut] class="space-below">
<rb-form-select [name]="'measurementTemplateSelect-' + gIndex + '-' + mIndex" label="Template"
[(ngModel)]="measurement.measurement_template"
(ngModelChange)="clearMeasurement(gIndex, mIndex)">
<option [value]="sample.condition.condition_template">
{{d.id.measurementTemplates[measurement.measurement_template].name}} - old
</option>
<option *ngFor="let m of d.latest.measurementTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<div *ngFor="let parameter of d.id.measurementTemplates[measurement.measurement_template].parameters;
index as pIndex">
<ng-container [ngSwitch]="(parameter.range.type ? 1 : 0) + (parameter.range.values ? 2 : 0)">
<rb-form-file *ngSwitchCase="1"
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" maxSize="10000000" multiple
[required]="measurement.values[parameter.name] &&
!measurement.values[parameter.name].length"
(ngModelChange)="fileToArray($event, gIndex, mIndex, parameter.name)"
placeholder="Select file or drag and drop" dragDrop ngModel>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-file>
<rb-form-select *ngSwitchCase="2"
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" [(ngModel)]="measurement.values[parameter.name]" ngModel>
<option *ngFor="let device of d.d.user.devices" [value]="device">{{device}}</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input *ngSwitchDefault
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" appValidate="string"
[(ngModel)]="measurement.values[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
<canvas baseChart *ngIf="parameter.range.type && charts[gIndex][mIndex][0].data.length > 0"
class="dpt-chart"
[@inOut]
[datasets]="charts[gIndex][mIndex]"
[labels]="[]"
[options]="chartOptions"
[legend]="false"
chartType="scatter">
</canvas>
</div>
<rb-icon-button icon="delete" mode="danger" (click)="removeMeasurement(gIndex, mIndex)">
Delete measurement
</rb-icon-button>
</div>
<div>
<rb-icon-button icon="add" mode="secondary" (click)="addMeasurement(gIndex)">
New measurement
</rb-icon-button>
</div>
</div>
</div>
<div class="buttons">
<rb-icon-button icon="summary" mode="primary" type="submit" (click)="view.cm = false; view.cmSum = true"
[disabled]="!cmForm.form.valid">
Summary
</rb-icon-button>
<rb-icon-button class="delete-sample" icon="delete" mode="danger" *ngIf="samples.length === 1"
(click)="deleteConfirm(modalDeleteConfirm)">
Delete sample
</rb-icon-button>
</div>
</form>
</div>
<div class="measurements shaded-container space-below">
<h5>
Measurements
<rb-icon-button icon="undo" mode="secondary" *ngIf="measurementRestoreData.length"
class="restore-measurements" (click)="restoreMeasurements()">
Restore measurements
</rb-icon-button>
</h5>
<div *ngFor="let measurement of sample.measurements; index as mIndex" [@inOut] class="space-below">
<rb-form-select [name]="'measurementTemplateSelect-' + gIndex + '-' + mIndex" label="Template"
[(ngModel)]="measurement.measurement_template"
(ngModelChange)="clearMeasurement(gIndex, mIndex)">
<option [value]="sample.condition.condition_template">
{{d.id.measurementTemplates[measurement.measurement_template].name}} - old
</option>
<option *ngFor="let m of d.latest.measurementTemplates" [value]="m._id">{{m.name}}</option>
</rb-form-select>
<div *ngFor="let parameter of d.id.measurementTemplates[measurement.measurement_template].parameters;
index as pIndex">
<ng-container [ngSwitch]="(parameter.range.type ? 1 : 0) + (parameter.range.values ? 2 : 0)">
<rb-form-file *ngSwitchCase="1"
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" maxSize="10000000" multiple
[required]="measurement.values[parameter.name] &&
!measurement.values[parameter.name].length"
(ngModelChange)="fileToArray($event, gIndex, mIndex, parameter.name)"
placeholder="Select file or drag and drop" dragDrop ngModel>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-file>
<rb-form-select *ngSwitchCase="2"
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" [(ngModel)]="measurement.values[parameter.name]" ngModel>
<option *ngFor="let device of d.d.user.devices" [value]="device">{{device}}</option>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-select>
<rb-form-input *ngSwitchDefault
[name]="'measurementParameter-' + gIndex + '-' + mIndex + '-' + pIndex"
[label]="parameter.name" appValidate="string"
[(ngModel)]="measurement.values[parameter.name]" #parameterInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
<ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
</rb-form-input>
</ng-container>
<canvas baseChart *ngIf="parameter.range.type && charts[gIndex][mIndex][0].data.length > 0"
class="dpt-chart"
[@inOut]
[datasets]="charts[gIndex][mIndex]"
[labels]="[]"
[options]="chartOptions"
[legend]="false"
chartType="scatter">
</canvas>
</div>
<rb-icon-button icon="delete" mode="danger" (click)="removeMeasurement(gIndex, mIndex)">
Delete measurement
</rb-icon-button>
</div>
<div>
<rb-icon-button icon="add" mode="secondary" (click)="addMeasurement(gIndex)">
New measurement
</rb-icon-button>
</div>
</div>
</div>
<div class="buttons">
<rb-icon-button icon="summary" mode="primary" type="submit" (click)="view.cm = false; view.cmSum = true"
[disabled]="!cmForm.form.valid">
Summary
</rb-icon-button>
<rb-icon-button class="delete-sample" icon="delete" mode="danger" *ngIf="samples.length === 1"
(click)="deleteConfirm(modalDeleteConfirm)">
Delete sample
</rb-icon-button>
</div>
</form>
</div>
<!--CM SUMMARY-->
<!--CM SUMMARY-->
<div *ngIf="view.cmSum">
<span class="rb-ic rb-ic-edit clickable" (click)="checkFormAfterInit = view.cm = true; view.cmSum = false">
</span>
<rb-table>
<ng-container *ngFor="let sample of samples; index as gIndex">
<tr><th>{{sample.number}}</th><th></th></tr>
<tr *ngFor="let parameter of (d.id.conditionTemplates[sample.condition.condition_template] || {parameters: []})
.parameters">
<td>{{parameter.name}}</td><td>{{sample.condition[parameter.name]}}</td>
</tr>
<ng-container *ngFor="let measurement of sample.measurements">
<tr *ngFor="let parameter of d.id.measurementTemplates[measurement.measurement_template].parameters">
<td>{{parameter.name}}</td><td>{{measurement.values[parameter.name]}}</td>
</tr>
</ng-container>
</ng-container>
</rb-table>
<rb-icon-button icon="save" mode="primary" type="submit" (click)="cmSave()">
Save sample{{samples.length > 1 ? 's' : ''}}
</rb-icon-button>
</div>
<div *ngIf="view.cmSum">
<span class="rb-ic rb-ic-edit clickable" (click)="checkFormAfterInit = view.cm = true; view.cmSum = false">
</span>
<rb-table>
<ng-container *ngFor="let sample of samples; index as gIndex">
<tr><th>{{sample.number}}</th><th></th></tr>
<tr *ngFor="let parameter of (d.id.conditionTemplates[sample.condition.condition_template] || {parameters: []})
.parameters">
<td>{{parameter.name}}</td><td>{{sample.condition[parameter.name]}}</td>
</tr>
<ng-container *ngFor="let measurement of sample.measurements">
<tr *ngFor="let parameter of d.id.measurementTemplates[measurement.measurement_template].parameters">
<td>{{parameter.name}}</td><td>{{measurement.values[parameter.name]}}</td>
</tr>
</ng-container>
</ng-container>
</rb-table>
<rb-icon-button icon="save" mode="primary" type="submit" (click)="cmSave()">
Save sample{{samples.length > 1 ? 's' : ''}}
</rb-icon-button>
</div>
</ng-template>
<ng-template #modalDeleteConfirm>
<rb-alert alertTitle="Are you sure?" type="danger" [okBtnLabel]="'Delete sample' + (samples.length > 1 ? 's' : '')"
cancelBtnLabel="Cancel">
Do you really want to delete {{(samples.length > 1 ? 'samples ' : 'sample ') + sampleNames()}}?
</rb-alert>
<rb-alert alertTitle="Are you sure?" type="danger" [okBtnLabel]="'Delete sample' + (samples.length > 1 ? 's' : '')"
cancelBtnLabel="Cancel">
Do you really want to delete {{(samples.length > 1 ? 'samples ' : 'sample ') + sampleNames()}}?
</rb-alert>
</ng-template>

View File

@ -1,49 +1,49 @@
::ng-deep rb-table#response-data > table {
width: auto !important;
width: auto !important;
}
td:first-child {
font-weight: bold;
font-weight: bold;
}
.condition-set {
float: right;
float: right;
}
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 10px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 10px;
}
.dpt-chart {
max-width: 400px;
max-width: 400px;
}
.sample-count {
max-width: 150px;
margin-right: 20px;
display: inline-block;
max-width: 150px;
margin-right: 20px;
display: inline-block;
}
.set-new-material {
display: block;
display: block;
}
.delete-sample {
float: right;
float: right;
}
.cm-form {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1rem;
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1rem;
rb-tab-panel, div.buttons {
grid-column: span 2;
}
rb-tab-panel, div.buttons {
grid-column: span 2;
}
}
.restore-measurements {
float: right;
float: right;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,292 +1,292 @@
<div class="header-addnew">
<a routerLink="/samples/new" *ngIf="login.isLevel.write">
<rb-icon-button icon="add" mode="primary" class="space-left">New sample</rb-icon-button>
</a>
<rb-icon-button *ngIf="sampleSelect === 2" mode="secondary" icon="close" (click)="sampleSelect = 0"
class="validation-close" iconOnly></rb-icon-button>
<rb-icon-button *ngIf="login.isLevel.dev" [icon]="sampleSelect === 2 ? 'checkmark' : 'clear-all'" mode="secondary"
(click)="validate()">
{{sampleSelect === 2 ? 'Validate' : 'Validation'}}
</rb-icon-button>
<a routerLink="/samples/new" *ngIf="login.isLevel.write">
<rb-icon-button icon="add" mode="primary" class="space-left">New sample</rb-icon-button>
</a>
<rb-icon-button *ngIf="sampleSelect === 2" mode="secondary" icon="close" (click)="sampleSelect = 0"
class="validation-close" iconOnly></rb-icon-button>
<rb-icon-button *ngIf="login.isLevel.dev" [icon]="sampleSelect === 2 ? 'checkmark' : 'clear-all'" mode="secondary"
(click)="validate()">
{{sampleSelect === 2 ? 'Validate' : 'Validation'}}
</rb-icon-button>
</div>
<!--FILTERS-->
<rb-accordion>
<rb-accordion-title [open]="false"><span class="rb-ic rb-ic-filter"></span>&nbsp; Filter</rb-accordion-title>
<rb-accordion-body>
<form class="filters">
<div class="status-selection">
<label class="label">Status</label>
<rb-form-checkbox name="status-validated" [(ngModel)]="filters.status.validated"
[disabled]="!filters.status.new && !filters.status.deleted" (ngModelChange)="loadSamples({firstPage: true})">
validated
</rb-form-checkbox>
<rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new"
[disabled]="!filters.status.validated && !filters.status.deleted"
(ngModelChange)="loadSamples({firstPage: true})">
new
</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>
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="filters.pageSize" class="selection"
(ngModelChange)="loadSamples({firstPage: true})" #pageSizeSelection>
<option value="3">3</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
</rb-form-select>
<rb-accordion-title [open]="false"><span class="rb-ic rb-ic-filter"></span>&nbsp; Filter</rb-accordion-title>
<rb-accordion-body>
<form class="filters">
<div class="status-selection">
<label class="label">Status</label>
<rb-form-checkbox name="status-validated" [(ngModel)]="filters.status.validated"
[disabled]="!filters.status.new && !filters.status.deleted" (ngModelChange)="loadSamples({firstPage: true})">
validated
</rb-form-checkbox>
<rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new"
[disabled]="!filters.status.validated && !filters.status.deleted"
(ngModelChange)="loadSamples({firstPage: true})">
new
</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>
<rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="filters.pageSize" class="selection"
(ngModelChange)="loadSamples({firstPage: true})" #pageSizeSelection>
<option value="3">3</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
</rb-form-select>
<div id="multi">
<rb-form-multi-select *ngFor="let item of categories | keyvalue: originalOrder" name="fieldSelect" idField="id"
[items]="item.value" [(ngModel)]="isActiveKey" [label]="item.key" class="selection"
(ngModelChange)="loadSamples({}, $event, item.key)">
<span *rbFormMultiSelectOption="let item" class="load-first-page">{{item.label}}</span>
</rb-form-multi-select>
</div>
<div id="multi">
<rb-form-multi-select *ngFor="let item of categories | keyvalue: originalOrder" name="fieldSelect" idField="id"
[items]="item.value" [(ngModel)]="isActiveKey" [label]="item.key" class="selection"
(ngModelChange)="loadSamples({}, $event, item.key)">
<span *rbFormMultiSelectOption="let item" class="load-first-page">{{item.label}}</span>
</rb-form-multi-select>
</div>
<rb-icon-button icon="reset" mode="secondary" (click)="resetPreferences()" class="reset-preferences">
Reset to default
</rb-icon-button>
<rb-icon-button icon="reset" mode="secondary" (click)="resetPreferences()" class="reset-preferences">
Reset to default
</rb-icon-button>
<div class="fieldfilters">
<div *ngFor="let filter of filters.filters">
<ng-container *ngIf="isActiveKey[filter.field]">
<rb-form-checkbox [name]="'filteractive-' + filter.field" [(ngModel)]="filter.active"></rb-form-checkbox>
<rb-form-select [name]="'filtermode-' + filter.field" class="filtermode" [(ngModel)]="filter.mode"
(ngModelChange)="updateFilterFields(filter.field)">
<option value="eq" title="field is equal to value">=</option>
<option value="ne" title="field is not equal to value">&ne;</option>
<option value="lt" title="field is lower than value">&lt;</option>
<option value="lte" title="field is lower than or equal to value">&le;</option>
<option value="gt" title="field is greater than value">&gt;</option>
<option value="gte" title="field is greater than or equal to value">&ge;</option>
<option value="stringin" title="field contains value">&supe;</option>
<option value="in" title="field is one of the values">&isin;</option>
<option value="nin" title="field is not one of the values">&notin;</option>
<option value="null" title="field is null">&empty;</option>
</rb-form-select>
<div class="filter-inputs">
<rb-array-input [(ngModel)]="filter.values" [name]="'filter-' + filter.field"
[pushTemplate]="!(filter.mode === 'in' || filter.mode === 'nin') ? null :''"
(ngModelChange)="updateFilterFields(filter.field)">
<ng-container *rbArrayInputItem="let item" [ngSwitch]="(filter.autocomplete.length ? 'autocomplete' : '') +
(filter.field == 'added' ? 'date' : (filter.field == 'type' ? 'type' : ''))">
<rb-form-date-input *ngSwitchCase="'date'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [disabled]="filter.mode === 'null'"></rb-form-date-input>
<rb-form-input *ngSwitchCase="''" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [disabled]="filter.mode === 'null'">
</rb-form-input>
<rb-form-input *ngSwitchCase="'autocomplete'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [rbDebounceTime]="0" (keydown)="preventDefault($event, 'Enter')"
[rbFormInputAutocomplete]="autocomplete.bind(this, filter.autocomplete)"
[disabled]="filter.mode === 'null'" ngModel></rb-form-input>
<rb-form-select *ngSwitchCase="'type'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value">
<option value="as-delivered/raw">as-delivered/raw</option>
<option value="processed">processed</option>
</rb-form-select>
</ng-container>
</rb-array-input>
</div>
</ng-container>
</div>
<rb-form-checkbox name="no-condition" [(ngModel)]="filters.no.condition" class="space-right">
has no condition
</rb-form-checkbox>
<rb-form-checkbox name="no-measurements" [(ngModel)]="filters.no.measurements" class="space-right">
has no measurements
</rb-form-checkbox>
</div>
</form>
<div class="fieldfilters">
<div *ngFor="let filter of filters.filters">
<ng-container *ngIf="isActiveKey[filter.field]">
<rb-form-checkbox [name]="'filteractive-' + filter.field" [(ngModel)]="filter.active"></rb-form-checkbox>
<rb-form-select [name]="'filtermode-' + filter.field" class="filtermode" [(ngModel)]="filter.mode"
(ngModelChange)="updateFilterFields(filter.field)">
<option value="eq" title="field is equal to value">=</option>
<option value="ne" title="field is not equal to value">&ne;</option>
<option value="lt" title="field is lower than value">&lt;</option>
<option value="lte" title="field is lower than or equal to value">&le;</option>
<option value="gt" title="field is greater than value">&gt;</option>
<option value="gte" title="field is greater than or equal to value">&ge;</option>
<option value="stringin" title="field contains value">&supe;</option>
<option value="in" title="field is one of the values">&isin;</option>
<option value="nin" title="field is not one of the values">&notin;</option>
<option value="null" title="field is null">&empty;</option>
</rb-form-select>
<div class="filter-inputs">
<rb-array-input [(ngModel)]="filter.values" [name]="'filter-' + filter.field"
[pushTemplate]="!(filter.mode === 'in' || filter.mode === 'nin') ? null :''"
(ngModelChange)="updateFilterFields(filter.field)">
<ng-container *rbArrayInputItem="let item" [ngSwitch]="(filter.autocomplete.length ? 'autocomplete' : '') +
(filter.field == 'added' ? 'date' : (filter.field == 'type' ? 'type' : ''))">
<rb-form-date-input *ngSwitchCase="'date'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [disabled]="filter.mode === 'null'"></rb-form-date-input>
<rb-form-input *ngSwitchCase="''" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [disabled]="filter.mode === 'null'">
</rb-form-input>
<rb-form-input *ngSwitchCase="'autocomplete'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value" [rbDebounceTime]="0" (keydown)="preventDefault($event, 'Enter')"
[rbFormInputAutocomplete]="autocomplete.bind(this, filter.autocomplete)"
[disabled]="filter.mode === 'null'" ngModel></rb-form-input>
<rb-form-select *ngSwitchCase="'type'" [rbArrayInputListener]="'filter-' + filter.field"
[name]="'filter-' + filter.field + item.i" [index]="item.i" [label]="filter.label"
[(ngModel)]="item.value">
<option value="as-delivered/raw">as-delivered/raw</option>
<option value="processed">processed</option>
</rb-form-select>
</ng-container>
</rb-array-input>
</div>
</ng-container>
</div>
<rb-form-checkbox name="no-condition" [(ngModel)]="filters.no.condition" class="space-right">
has no condition
</rb-form-checkbox>
<rb-form-checkbox name="no-measurements" [(ngModel)]="filters.no.measurements" class="space-right">
has no measurements
</rb-form-checkbox>
</div>
</form>
<rb-icon-button icon="forward-right" mode="primary" (click)="loadSamples({firstPage: true})">
Apply filters
</rb-icon-button>
</rb-accordion-body>
<rb-icon-button icon="forward-right" mode="primary" (click)="loadSamples({firstPage: true})">
Apply filters
</rb-icon-button>
</rb-accordion-body>
</rb-accordion>
<ng-container *ngTemplateOutlet="paging"></ng-container>
<div class="samples-loading" *ngIf="loading">
<rb-loading-spinner></rb-loading-spinner>
<div>Loading...</div>
<rb-loading-spinner></rb-loading-spinner>
<div>Loading...</div>
</div>
<!--DOWNLOAD BUTTONS-->
<div class="download space-below" *ngIf="login.isLevel.dev">
<rb-icon-button class="space-right" icon="download" mode="secondary" [rbModal]="linkModal">
JSON download link
</rb-icon-button>
<ng-template #linkModal>
<div class="link-dialog">
<label for="jsonUrl">URL for JSON download</label>
<textarea class="linkmodal" id="jsonUrl" #linkarea [value]="sampleUrl({export: true, host: true})"
(keydown)="preventDefault($event)"></textarea>
<rb-form-checkbox class="space-right" name="download-spectra" [(ngModel)]="downloadSpectra">
add spectra
</rb-form-checkbox>
<rb-form-checkbox class="space-right" name="download-conditions" [(ngModel)]="downloadCondition">
add conditions
</rb-form-checkbox>
<rb-form-checkbox class="space-right" name="download-flatten" [(ngModel)]="downloadFlatten">
flatten object
</rb-form-checkbox>
<rb-icon-button icon="clipboard" mode="secondary" (click)="clipboard()">Copy to clipboard</rb-icon-button>
</div>
</ng-template>
<a [href]="csvUrl" download="samples.csv">
<rb-icon-button icon="download" mode="secondary" (mousedown)="csvUrl = sampleUrl({csv: true, export: true})">
Download result as CSV
</rb-icon-button>
</a>
<rb-icon-button class="space-right" icon="download" mode="secondary" [rbModal]="linkModal">
JSON download link
</rb-icon-button>
<ng-template #linkModal>
<div class="link-dialog">
<label for="jsonUrl">URL for JSON download</label>
<textarea class="linkmodal" id="jsonUrl" #linkarea [value]="sampleUrl({export: true, host: true})"
(keydown)="preventDefault($event)"></textarea>
<rb-form-checkbox class="space-right" name="download-spectra" [(ngModel)]="downloadSpectra">
add spectra
</rb-form-checkbox>
<rb-form-checkbox class="space-right" name="download-conditions" [(ngModel)]="downloadCondition">
add conditions
</rb-form-checkbox>
<rb-form-checkbox class="space-right" name="download-flatten" [(ngModel)]="downloadFlatten">
flatten object
</rb-form-checkbox>
<rb-icon-button icon="clipboard" mode="secondary" (click)="clipboard()">Copy to clipboard</rb-icon-button>
</div>
</ng-template>
<a [href]="csvUrl" download="samples.csv">
<rb-icon-button icon="download" mode="secondary" (mousedown)="csvUrl = sampleUrl({csv: true, export: true})">
Download result as CSV
</rb-icon-button>
</a>
</div>
<!--TABLE-->
<rb-table class="samples-table" scrollTop ellipsis>
<tr>
<th *ngIf="login.isLevel.write">
<span class="rb-ic rb-ic-edit clickable" *ngIf="login.isLevel.dev" (click)="batchEdit()"></span>
<span class="rb-ic rb-ic-close clickable" *ngIf="sampleSelect === 1" (click)="sampleSelect = 0"></span>
</th>
<th *ngIf="sampleSelect">
<rb-form-checkbox name="select-all" (change)="selectAll($event)">all</rb-form-checkbox>
</th>
<th *ngFor="let key of activeKeys" [title]="key.label">
<div class="sort-header">
<span>{{key.label}}</span>
<div *ngIf="key.sortable">
<span class="rb-ic rb-ic-up sort-arr-up" (click)="setSort(key.id + '-' + 'desc')">
<span *ngIf="filters.sort === key.id + '-' + 'desc'"></span>
</span>
<span class="rb-ic rb-ic-down sort-arr-down" (click)="setSort(key.id + '-' + 'asc')">
<span *ngIf="filters.sort === key.id + '-' + 'asc'"></span>
</span>
</div>
</div>
</th>
</tr>
<tr>
<th *ngIf="login.isLevel.write">
<span class="rb-ic rb-ic-edit clickable" *ngIf="login.isLevel.dev" (click)="batchEdit()"></span>
<span class="rb-ic rb-ic-close clickable" *ngIf="sampleSelect === 1" (click)="sampleSelect = 0"></span>
</th>
<th *ngIf="sampleSelect">
<rb-form-checkbox name="select-all" (change)="selectAll($event)">all</rb-form-checkbox>
</th>
<th *ngFor="let key of activeKeys" [title]="key.label">
<div class="sort-header">
<span>{{key.label}}</span>
<div *ngIf="key.sortable">
<span class="rb-ic rb-ic-up sort-arr-up" (click)="setSort(key.id + '-' + 'desc')">
<span *ngIf="filters.sort === key.id + '-' + 'desc'"></span>
</span>
<span class="rb-ic rb-ic-down sort-arr-down" (click)="setSort(key.id + '-' + 'asc')">
<span *ngIf="filters.sort === key.id + '-' + 'asc'"></span>
</span>
</div>
</div>
</th>
</tr>
<tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
<td *ngIf="login.isLevel.write">
<a [routerLink]="'/samples/edit/' + sample._id" *ngIf="sample.status !== 'deleted' &&
(login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId))">
<span class="rb-ic rb-ic-edit clickable"></span>
</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 *ngIf="sampleSelect">
<rb-form-checkbox *ngIf="sample.status !== 'deleted'" [name]="'validate-' + i" (click)="stopPropagation($event)"
[(ngModel)]="sample.selected">
</rb-form-checkbox>
</td>
<td *ngFor="let item of activeKeys; index as j">
{{data[i].data[j]}}
</td>
</tr>
<tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
<td *ngIf="login.isLevel.write">
<a [routerLink]="'/samples/edit/' + sample._id" *ngIf="sample.status !== 'deleted' &&
(login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId))">
<span class="rb-ic rb-ic-edit clickable"></span>
</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 *ngIf="sampleSelect">
<rb-form-checkbox *ngIf="sample.status !== 'deleted'" [name]="'validate-' + i" (click)="stopPropagation($event)"
[(ngModel)]="sample.selected">
</rb-form-checkbox>
</td>
<td *ngFor="let item of activeKeys; index as j">
{{data[i].data[j]}}
</td>
</tr>
</rb-table>
<ng-container *ngTemplateOutlet="paging"></ng-container>
<ng-template #paging>
<div class="paging">
<button class="rb-btn rb-link" type="button" (click)="loadPage(-1)" [disabled]="page === 1">
<span class="rb-ic rb-ic-back-left"></span>
</button>
<rb-form-input label="page" (ngModelChange)="loadPage($event - page)" [ngModel]="page">
</rb-form-input>
<span>
of {{pages}} ({{totalSamples}} samples)
</span>
<button class="rb-btn rb-link" type="button" (click)="loadPage(1)" [disabled]="page >= pages">
<span class="rb-ic rb-ic-forward-right"></span>
</button>
</div>
<div class="paging">
<button class="rb-btn rb-link" type="button" (click)="loadPage(-1)" [disabled]="page === 1">
<span class="rb-ic rb-ic-back-left"></span>
</button>
<rb-form-input label="page" (ngModelChange)="loadPage($event - page)" [ngModel]="page">
</rb-form-input>
<span>
of {{pages}} ({{totalSamples}} samples)
</span>
<button class="rb-btn rb-link" type="button" (click)="loadPage(1)" [disabled]="page >= pages">
<span class="rb-ic rb-ic-forward-right"></span>
</button>
</div>
</ng-template>
<!--SAMPLE DETAILS-->
<ng-template #sampleModal>
<rb-loading-spinner *ngIf="sampleDetailsSample === null; else sampleDetailsTemplate"></rb-loading-spinner>
<ng-template #sampleDetailsTemplate>
<h3>{{sampleDetailsSample.number}}</h3>
<rb-table class="sample-details-table">
<tr>
<th>Material</th>
<td>{{sampleDetailsSample.material.name}}</td>
</tr>
<tr>
<th>Supplier</th>
<td>{{sampleDetailsSample.material.supplier}}</td>
</tr>
<tr>
<th>Group</th>
<td>{{sampleDetailsSample.material.group}}</td>
</tr>
<tr>
<th>Type</th>
<td>{{sampleDetailsSample.type}}</td>
</tr>
<tr>
<th>color</th>
<td>{{sampleDetailsSample.color}}</td>
</tr>
<tr>
<th>Batch</th>
<td>{{sampleDetailsSample.batch}}</td>
</tr>
<tr>
<th>Comment</th>
<td>{{sampleDetailsSample.notes.comment | exists}}</td>
</tr>
<tr *ngFor="let customField of sampleDetailsSample.notes.custom_fields_entries">
<th>{{customField[0]}}</th>
<td>{{customField[1]}}</td>
</tr>
<tr *ngFor="let reference of sampleDetailsSample.notes.sample_references">
<th>{{reference.relation}}</th>
<td>
<button class="rb-btn rb-link" (click)="sampleDetails(reference.sample_id, sampleModal)">
{{reference.number}}
</button>
</td>
</tr>
<tr *ngFor="let conditionField of sampleDetailsSample.condition_entries">
<th>{{conditionField[0]}}</th>
<td>{{conditionField[1]}}</td>
</tr>
<tr *ngFor="let measurement of sampleDetailsSample.measurement_entries">
<th>{{measurement.name}}</th>
<td>{{measurement.value}}</td>
</tr>
<tr>
<th>User</th>
<td>{{sampleDetailsSample.user}}</td>
</tr>
<tr>
<th>Status</th>
<td>{{sampleDetailsSample.status}}</td>
</tr>
</rb-table>
</ng-template>
<rb-loading-spinner *ngIf="sampleDetailsSample === null; else sampleDetailsTemplate"></rb-loading-spinner>
<ng-template #sampleDetailsTemplate>
<h3>{{sampleDetailsSample.number}}</h3>
<rb-table class="sample-details-table">
<tr>
<th>Material</th>
<td>{{sampleDetailsSample.material.name}}</td>
</tr>
<tr>
<th>Supplier</th>
<td>{{sampleDetailsSample.material.supplier}}</td>
</tr>
<tr>
<th>Group</th>
<td>{{sampleDetailsSample.material.group}}</td>
</tr>
<tr>
<th>Type</th>
<td>{{sampleDetailsSample.type}}</td>
</tr>
<tr>
<th>color</th>
<td>{{sampleDetailsSample.color}}</td>
</tr>
<tr>
<th>Batch</th>
<td>{{sampleDetailsSample.batch}}</td>
</tr>
<tr>
<th>Comment</th>
<td>{{sampleDetailsSample.notes.comment | exists}}</td>
</tr>
<tr *ngFor="let customField of sampleDetailsSample.notes.custom_fields_entries">
<th>{{customField[0]}}</th>
<td>{{customField[1]}}</td>
</tr>
<tr *ngFor="let reference of sampleDetailsSample.notes.sample_references">
<th>{{reference.relation}}</th>
<td>
<button class="rb-btn rb-link" (click)="sampleDetails(reference.sample_id, sampleModal)">
{{reference.number}}
</button>
</td>
</tr>
<tr *ngFor="let conditionField of sampleDetailsSample.condition_entries">
<th>{{conditionField[0]}}</th>
<td>{{conditionField[1]}}</td>
</tr>
<tr *ngFor="let measurement of sampleDetailsSample.measurement_entries">
<th>{{measurement.name}}</th>
<td>{{measurement.value}}</td>
</tr>
<tr>
<th>User</th>
<td>{{sampleDetailsSample.user}}</td>
</tr>
<tr>
<th>Status</th>
<td>{{sampleDetailsSample.status}}</td>
</tr>
</rb-table>
</ng-template>
</ng-template>
<ng-template #restoreConfirm>
<rb-dialog dialogTitle="Restore sample">
Do you really want to restore this sample?
</rb-dialog>
</ng-template>
<rb-dialog dialogTitle="Restore sample">
Do you really want to restore this sample?
</rb-dialog>
</ng-template>

View File

@ -1,249 +1,249 @@
@import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors";
.header-addnew {
margin-bottom: 40px;
height: 42px;
margin-bottom: 40px;
height: 42px;
& > * {
display: inline;
margin-bottom: 10px;
}
& > * {
display: inline;
margin-bottom: 10px;
}
rb-icon-button {
float: right;
}
rb-icon-button {
float: right;
}
}
rb-table {
width: 100%;
width: 100%;
}
td .clickable.rb-ic {
font-size: 1.1rem;
color: $color-gray-silver-sand;
cursor: pointer;
td .clickable.rb-ic {
font-size: 1.1rem;
color: $color-gray-silver-sand;
cursor: pointer;
&:hover {
color: #000;
}
&:hover {
color: #000;
}
}
rb-form-multi-select{
min-width: 9rem;
min-width: 9rem;
}
.status-selection {
overflow: hidden;
margin-bottom: 10px;
float: left;
margin-right: 15px;
overflow: hidden;
margin-bottom: 10px;
float: left;
margin-right: 15px;
label {
display: block;
font-weight: 700;
font-size: 10px;
}
label {
display: block;
font-weight: 700;
font-size: 10px;
}
rb-form-checkbox {
float: left;
margin-right: 10px;
margin-top: -10px;
}
rb-form-checkbox {
float: left;
margin-right: 10px;
margin-top: -10px;
}
}
.selection {
max-width: 230px;
float: left;
max-width: 230px;
float: left;
}
.paging {
height: 50px;
float: left;
height: 50px;
float: left;
rb-form-input {
max-width: 65px;
}
rb-form-input {
max-width: 65px;
}
> * {
float: left;
}
> * {
float: left;
}
> button {
margin-top: 18px;
}
> button {
margin-top: 18px;
}
> span {
margin-top: 20px;
margin-left: 5px;
}
> span {
margin-top: 20px;
margin-left: 5px;
}
}
.sort-header {
width: 100%;
position: relative;
width: 100%;
position: relative;
& > span:first-child {
max-width: 180px;
overflow: hidden;
display: block;
text-overflow: ellipsis;
margin-right: 20px;
}
& > span:first-child {
max-width: 180px;
overflow: hidden;
display: block;
text-overflow: ellipsis;
margin-right: 20px;
}
div {
display: grid;
grid-template-columns: 1fr;
position: absolute;
right: 0;
top: 0;
background: #FFF;
div {
display: grid;
grid-template-columns: 1fr;
position: absolute;
right: 0;
top: 0;
background: #FFF;
:nth-child(1) {
margin-bottom: -3px;
cursor: pointer;
}
:nth-child(1) {
margin-bottom: -3px;
cursor: pointer;
}
:nth-child(2) {
margin-top: -3px;
cursor: pointer;
}
}
:nth-child(2) {
margin-top: -3px;
cursor: pointer;
}
}
}
.filters:after {
content:"";
clear:both;
display:block;
content:"";
clear:both;
display:block;
}
.download {
margin-top: 5px;
float: right;
margin-top: 5px;
float: right;
& > rb-form-checkbox {
display: inline-block;
}
& > rb-form-checkbox {
display: inline-block;
}
button {
margin-right: 10px;
}
button {
margin-right: 10px;
}
}
.sort-arr-up {
position: relative;
position: relative;
& > span {
width: 0;
height: 0;
border-left: 6.3px solid transparent;
border-right: 6.3px solid transparent;
border-bottom: 6.3px solid #000;
position: absolute;
top: 5px;
display: block;
left: 2px;
}
& > span {
width: 0;
height: 0;
border-left: 6.3px solid transparent;
border-right: 6.3px solid transparent;
border-bottom: 6.3px solid #000;
position: absolute;
top: 5px;
display: block;
left: 2px;
}
}
.sort-arr-down {
position: relative;
position: relative;
& > span {
width: 0;
height: 0;
border-left: 6.3px solid transparent;
border-right: 6.3px solid transparent;
border-top: 6.3px solid #000;
position: absolute;
top: 5px;
display: block;
left: 2px;
}
& > span {
width: 0;
height: 0;
border-left: 6.3px solid transparent;
border-right: 6.3px solid transparent;
border-top: 6.3px solid #000;
position: absolute;
top: 5px;
display: block;
left: 2px;
}
}
.fieldfilters {
clear: both;
clear: both;
& > div {
display: grid;
grid-template-columns: auto auto 1fr;
float: left;
margin-right: 30px;
}
& > div {
display: grid;
grid-template-columns: auto auto 1fr;
float: left;
margin-right: 30px;
}
& > rb-form-checkbox {
float: left;
}
& > rb-form-checkbox {
float: left;
}
}
.filtermode {
max-width: 82px;
max-width: 82px;
}
textarea.linkmodal {
display: block;
min-width: 600px;
min-height: 200px;
border: none;
display: block;
min-width: 600px;
min-height: 200px;
border: none;
}
.filter-inputs > * {
display: inline-block;
width: 220px;
display: inline-block;
width: 220px;
}
.sample-details-table {
td {
max-width: none;
}
td {
max-width: none;
}
}
.validation-close {
margin-left: -1px;
margin-left: -1px;
}
.samples-table tr.clickable {
background: none;
transition: background-color 0.5s;
background: none;
transition: background-color 0.5s;
&:hover {
background: $color-gray-mercury;
}
&:hover {
background: $color-gray-mercury;
}
}
::ng-deep .samples-table rb-form-checkbox .input-wrapper {
padding-top: 0;
margin-top: -4.5px;
padding-top: 0;
margin-top: -4.5px;
}
.link-dialog {
rb-form-checkbox {
display: inline-block;
}
rb-form-checkbox {
display: inline-block;
}
rb-icon-button {
float: right;
}
rb-icon-button {
float: right;
}
}
.samples-loading {
float: left;
display: grid;
grid-template-columns: auto auto;
grid-template-rows: 1fr auto 1fr;
float: left;
display: grid;
grid-template-columns: auto auto;
grid-template-rows: 1fr auto 1fr;
rb-loading-spinner {
grid-row: span 3;
transform: scale(0.5);
}
rb-loading-spinner {
grid-row: span 3;
transform: scale(0.5);
}
& > div {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
& > div {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
}
.reset-preferences {
float: right;
float: right;
}

File diff suppressed because it is too large Load Diff

View File

@ -7,93 +7,93 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class ApiService {
private host = isDevMode() ? '/api' : 'https://definma-api.apps.de1.bosch-iot-cloud.com';
private host = isDevMode() ? '/api' : 'https://definma-api.apps.de1.bosch-iot-cloud.com';
constructor(
private http: HttpClient,
private storage: LocalStorageService,
private modalService: ModalService,
private window: Window
) { }
constructor(
private http: HttpClient,
private storage: LocalStorageService,
private modalService: ModalService,
private window: Window
) { }
get hostName() {
return this.host;
}
get hostName() {
return this.host;
}
// Main HTTP methods
get<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.get(this.url(url), this.options()), f);
}
// Main HTTP methods
get<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.get(this.url(url), this.options()), f);
}
post<T>(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.post(this.url(url), data, this.options()), f);
}
post<T>(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.post(this.url(url), data, this.options()), f);
}
put<T>(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.put(this.url(url), data, this.options()), f);
}
put<T>(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.put(this.url(url), data, this.options()), f);
}
delete<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.delete(this.url(url), this.options()), f);
}
delete<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.delete(this.url(url), this.options()), f);
}
// Execute request and handle errors
private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?, headers?) => void) {
observable.subscribe(data => { // Successful request
f(
data.body,
undefined,
data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {})
);
}, err => { // Error
if (f.length > 1) { // Pass on error
f(undefined, err, undefined);
}
else { // Handle directly
this.requestError(err);
}
});
}
// Execute request and handle errors
private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?, headers?) => void) {
observable.subscribe(data => { // Successful request
f(
data.body,
undefined,
data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {})
);
}, err => { // Error
if (f.length > 1) { // Pass on error
f(undefined, err, undefined);
}
else { // Handle directly
this.requestError(err);
}
});
}
requestError(err) { // Network error dialog
const modalRef = this.modalService.openComponent(ErrorComponent);
modalRef.instance.message = 'Network request failed!';
const details = [err.error.status];
if (err.error.details) {
details.push(err.error.details);
}
modalRef.instance.details = details;
modalRef.result.then(() => {
this.window.location.reload();
});
}
requestError(err) { // Network error dialog
const modalRef = this.modalService.openComponent(ErrorComponent);
modalRef.instance.message = 'Network request failed!';
const details = [err.error.status];
if (err.error.details) {
details.push(err.error.details);
}
modalRef.instance.details = details;
modalRef.result.then(() => {
this.window.location.reload();
});
}
private url(url) { // Detect if host was given, otherwise use default host
if (/http[s]?:\/\//.test(url)) {
return url;
}
else {
return this.host + url;
}
}
// Generate request options
private options(): {headers: HttpHeaders, observe: 'body'} {
return {headers: this.authOptions(), observe: 'response' as 'body'};
}
// Generate Basic Auth
private authOptions(): HttpHeaders {
const auth = this.storage.get('basicAuth');
if (auth) {
return new HttpHeaders({Authorization: 'Basic ' + auth});
}
else {
return new HttpHeaders();
}
}
private url(url) { // Detect if host was given, otherwise use default host
if (/http[s]?:\/\//.test(url)) {
return url;
}
else {
return this.host + url;
}
}
// Generate request options
private options(): {headers: HttpHeaders, observe: 'body'} {
return {headers: this.authOptions(), observe: 'response' as 'body'};
}
// Generate Basic Auth
private authOptions(): HttpHeaders {
const auth = this.storage.get('basicAuth');
if (auth) {
return new HttpHeaders({Authorization: 'Basic ' + auth});
}
else {
return new HttpHeaders();
}
}
}

View File

@ -3,18 +3,18 @@ import {QuickScore} from 'quick-score';
import {of} from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class AutocompleteService {
constructor() { }
constructor() { }
bind(ref, list: string[]) {
return this.search.bind(ref, list);
}
bind(ref, list: string[]) {
return this.search.bind(ref, list);
}
search(arr: string[], str: string) {
const qs = new QuickScore(arr);
return of(str === '' ? [] : qs.search(str).map(e => e.item));
}
search(arr: string[], str: string) {
const qs = new QuickScore(arr);
return of(str === '' ? [] : qs.search(str).map(e => e.item));
}
}

View File

@ -8,77 +8,77 @@ import {ModelItemModel} from '../models/model-item.model';
import {ModelFileModel} from '../models/model-file.model';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class DataService {
constructor(
private api: ApiService
) { }
constructor(
private api: ApiService
) { }
private collectionMap = { // List of available collections
materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'},
materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'},
materialGroups: {path: '/material/groups', model: null, type: 'idArray'},
materialTemplates: {path: '/template/materials', model: TemplateModel, type: 'template'},
measurementTemplates: {path: '/template/measurements', model: TemplateModel, type: 'template'},
conditionTemplates: {path: '/template/conditions', model: TemplateModel, type: 'template'},
sampleNotesFields: {path: '/sample/notes/fields', model: TemplateModel, type: 'idArray'},
users: {path: '/users', model: UserModel, type: 'idArray'},
modelGroups: {path: '/model/groups', model: ModelItemModel, type: 'array'},
modelFiles: {path: '/model/files', model: ModelFileModel, type: 'array'},
user: {path: '/user', model: UserModel, type: 'string'},
userKey: {path: '/user/key', model: BaseModel, type: 'string'}
};
private collectionMap = { // List of available collections
materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'},
materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'},
materialGroups: {path: '/material/groups', model: null, type: 'idArray'},
materialTemplates: {path: '/template/materials', model: TemplateModel, type: 'template'},
measurementTemplates: {path: '/template/measurements', model: TemplateModel, type: 'template'},
conditionTemplates: {path: '/template/conditions', model: TemplateModel, type: 'template'},
sampleNotesFields: {path: '/sample/notes/fields', model: TemplateModel, type: 'idArray'},
users: {path: '/users', model: UserModel, type: 'idArray'},
modelGroups: {path: '/model/groups', model: ModelItemModel, type: 'array'},
modelFiles: {path: '/model/files', model: ModelFileModel, type: 'array'},
user: {path: '/user', model: UserModel, type: 'string'},
userKey: {path: '/user/key', model: BaseModel, type: 'string'}
};
arr: {[key: string]: any[]} = {}; // Array of data
latest: {[key: string]: any[]} = {}; // Array of latest template versions
id: {[key: string]: {[id: string]: any}} = {}; // Data in format _id: data
d: {[key: string]: any} = {}; // Data not in array format
arr: {[key: string]: any[]} = {}; // Array of data
latest: {[key: string]: any[]} = {}; // Array of latest template versions
id: {[key: string]: {[id: string]: any}} = {}; // Data in format _id: data
d: {[key: string]: any} = {}; // Data not in array format
contact = {name: 'CR/APS1-Lingenfelser', mail: 'dominic.lingenfelser@bosch.com'}; // Global contact data
contact = {name: 'CR/APS1-Lingenfelser', mail: 'dominic.lingenfelser@bosch.com'}; // Global contact data
load(collection, f = () => {}) { // Load data
if (this.arr[collection]) { // Data already loaded
f();
}
else { // Load data
this.api.get<any>(this.collectionMap[collection].path, data => {
if (this.collectionMap[collection].type !== 'string') { // Array data
this.arr[collection] = data
.map(
e => this.collectionMap[collection].model ?
new this.collectionMap[collection].model().deserialize(e) : e
);
// Load ids
if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') {
this.idReload(collection);
}
}
else { // Not array data
this.d[collection] = new this.collectionMap[collection].model().deserialize(data);
}
f();
});
}
}
load(collection, f = () => {}) { // Load data
if (this.arr[collection]) { // Data already loaded
f();
}
else { // Load data
this.api.get<any>(this.collectionMap[collection].path, data => {
if (this.collectionMap[collection].type !== 'string') { // Array data
this.arr[collection] = data
.map(
e => this.collectionMap[collection].model ?
new this.collectionMap[collection].model().deserialize(e) : e
);
// Load ids
if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') {
this.idReload(collection);
}
}
else { // Not array data
this.d[collection] = new this.collectionMap[collection].model().deserialize(data);
}
f();
});
}
}
// Generate id object
idReload(collection) {
this.id[collection] = this.arr[collection].reduce((s, e) => {s[e._id] = e; return s; }, {});
if (this.collectionMap[collection].type === 'template') { // Generate array with latest templates
const tmpTemplates = {};
this.arr[collection].forEach(template => {
if (tmpTemplates[template.first_id]) { // Already found another version
if (template.version > tmpTemplates[template.first_id].version) {
tmpTemplates[template.first_id] = template;
}
}
else {
tmpTemplates[template.first_id] = template;
}
});
this.latest[collection] = Object.values(tmpTemplates);
}
}
// Generate id object
idReload(collection) {
this.id[collection] = this.arr[collection].reduce((s, e) => {s[e._id] = e; return s; }, {});
if (this.collectionMap[collection].type === 'template') { // Generate array with latest templates
const tmpTemplates = {};
this.arr[collection].forEach(template => {
if (tmpTemplates[template.first_id]) { // Already found another version
if (template.version > tmpTemplates[template.first_id].version) {
tmpTemplates[template.first_id] = template;
}
}
else {
tmpTemplates[template.first_id] = template;
}
});
this.latest[collection] = Object.values(tmpTemplates);
}
}
}

View File

@ -6,130 +6,130 @@ import {Observable} from 'rxjs';
import {DataService} from './data.service';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class LoginService implements CanActivate {
private pathPermissions = [ // Minimum level needed for the specified paths
{path: 'materials', permission: 'dev'},
{path: 'templates', permission: 'dev'},
{path: 'changelog', permission: 'dev'},
{path: 'users', permission: 'admin'}
];
readonly levels = [ // All user levels in ascending permissions order
'predict',
'read',
'write',
'dev',
'admin'
];
// Returns true or false depending on whether the user fulfills the minimum level
isLevel: {[level: string]: boolean} = {};
hasPrediction = false; // True if user has prediction models specified
userId = '';
private pathPermissions = [ // Minimum level needed for the specified paths
{path: 'materials', permission: 'dev'},
{path: 'templates', permission: 'dev'},
{path: 'changelog', permission: 'dev'},
{path: 'users', permission: 'admin'}
];
readonly levels = [ // All user levels in ascending permissions order
'predict',
'read',
'write',
'dev',
'admin'
];
// Returns true or false depending on whether the user fulfills the minimum level
isLevel: {[level: string]: boolean} = {};
hasPrediction = false; // True if user has prediction models specified
userId = '';
private loggedIn;
private loggedIn;
constructor(
private api: ApiService,
private storage: LocalStorageService,
private router: Router,
private d: DataService
) {
constructor(
private api: ApiService,
private storage: LocalStorageService,
private router: Router,
private d: DataService
) {
}
}
login(username = '', password = '') {
return new Promise(resolve => {
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));
}
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));
}
}
this.api.get('/authorized', (data: any, error) => {
if (!error) {
if (data.status === 'Authorization successful') {
this.loggedIn = true;
this.levels.forEach(level => {
this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level);
if (this.isLevel.dev) { // Set hasPrediction
this.hasPrediction = true;
}
else {
this.d.load('modelGroups', () => {
this.hasPrediction = this.d.arr.modelGroups.length > 0;
});
}
});
this.userId = data.user_id;
resolve(true);
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
});
});
}
login(username = '', password = '') {
return new Promise(resolve => {
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));
}
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));
}
}
this.api.get('/authorized', (data: any, error) => {
if (!error) {
if (data.status === 'Authorization successful') {
this.loggedIn = true;
this.levels.forEach(level => {
this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level);
if (this.isLevel.dev) { // Set hasPrediction
this.hasPrediction = true;
}
else {
this.d.load('modelGroups', () => {
this.hasPrediction = this.d.arr.modelGroups.length > 0;
});
}
});
this.userId = data.user_id;
resolve(true);
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
});
});
}
logout() {
this.storage.remove('basicAuth');
this.loggedIn = false;
this.levels.forEach(level => {
this.isLevel[level] = false;
});
this.hasPrediction = false;
}
logout() {
this.storage.remove('basicAuth');
this.loggedIn = false;
this.levels.forEach(level => {
this.isLevel[level] = false;
});
this.hasPrediction = false;
}
// CanActivate for Angular routing
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
return new Observable<boolean>(observer => {
new Promise(resolve => {
if (this.loggedIn === undefined) {
this.login().then(res => {
resolve(res);
});
}
else {
resolve(this.loggedIn);
}
}).then(res => {
const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0);
// Check if level is permitted for path
const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]);
observer.next(ok);
observer.complete();
if (!ok) {
this.router.navigate(['/']);
}
});
});
}
// CanActivate for Angular routing
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
return new Observable<boolean>(observer => {
new Promise(resolve => {
if (this.loggedIn === undefined) {
this.login().then(res => {
resolve(res);
});
}
else {
resolve(this.loggedIn);
}
}).then(res => {
const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0);
// Check if level is permitted for path
const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]);
observer.next(ok);
observer.complete();
if (!ok) {
this.router.navigate(['/']);
}
});
});
}
get isLoggedIn() {
return this.loggedIn;
}
get isLoggedIn() {
return this.loggedIn;
}
get username() {
return atob(this.storage.get('basicAuth')).split(':')[0];
}
get username() {
return atob(this.storage.get('basicAuth')).split(':')[0];
}
}

View File

@ -3,183 +3,183 @@ import Joi from '@hapi/joi';
import {AbstractControl} from '@angular/forms';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class ValidationService {
private vUsername = Joi.string()
.lowercase()
.pattern(new RegExp('^[a-z0-9-_.]+$'))
.min(1)
.max(128);
private vUsername = Joi.string()
.lowercase()
.pattern(new RegExp('^[a-z0-9-_.]+$'))
.min(1)
.max(128);
private vPassword = Joi.string()
.min(8)
.max(128);
private vPassword = Joi.string()
.min(8)
.max(128);
constructor() { }
constructor() { }
generate(method, args) { // Generate a Validator function
return (control: AbstractControl): {[key: string]: any} | null => {
let ok;
let error;
if (args) {
({ok, error} = this[method](control.value, ...args));
}
else {
({ok, error} = this[method](control.value));
}
return ok ? null : { failure: error };
};
}
generate(method, args) { // Generate a Validator function
return (control: AbstractControl): {[key: string]: any} | null => {
let ok;
let error;
if (args) {
({ok, error} = this[method](control.value, ...args));
}
else {
({ok, error} = this[method](control.value));
}
return ok ? null : { failure: error };
};
}
username(data) {
const {ignore, error} = this.vUsername.validate(data);
if (error) {
return {ok: false, error: 'username must only contain a-z0-9-_.'};
}
return {ok: true, error: ''};
}
username(data) {
const {ignore, error} = this.vUsername.validate(data);
if (error) {
return {ok: false, error: 'username must only contain a-z0-9-_.'};
}
return {ok: true, error: ''};
}
password(data) {
const {ignore, error} = this.vPassword.validate(data);
if (error) {
return {ok: false, error: 'password must have at least 8 characters'};
}
return {ok: true, error: ''};
}
password(data) {
const {ignore, error} = this.vPassword.validate(data);
if (error) {
return {ok: false, error: 'password must have at least 8 characters'};
}
return {ok: true, error: ''};
}
string(data, option = null) {
let validator = Joi.string().max(128).allow('', true, false);
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) {
return {ok: false, error: errorMsg};
}
return {ok: true, error: ''};
}
string(data, option = null) {
let validator = Joi.string().max(128).allow('', true, false);
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) {
return {ok: false, error: errorMsg};
}
return {ok: true, error: ''};
}
stringOf(data, list) { // String must be in list
const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: 'must be one of ' + list.join(', ')};
}
return {ok: true, error: ''};
}
stringOf(data, list) { // String must be in list
const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: 'must be one of ' + list.join(', ')};
}
return {ok: true, error: ''};
}
stringNin(data, list) { // String must not be in list
const {ignore, error} = Joi.string().invalid(...list).validate(data);
if (error) {
return {ok: false, error: 'value not allowed'};
}
return {ok: true, error: ''};
}
stringNin(data, list) { // String must not be in list
const {ignore, error} = Joi.string().invalid(...list).validate(data);
if (error) {
return {ok: false, error: 'value not allowed'};
}
return {ok: true, error: ''};
}
stringLength(data, length) { // String with maximum length
const {ignore, error} = Joi.string().max(length).allow('').validate(data);
if (error) {
return {ok: false, error: 'must contain max ' + length + ' characters'};
}
return {ok: true, error: ''};
}
stringLength(data, length) { // String with maximum length
const {ignore, error} = Joi.string().max(length).allow('').validate(data);
if (error) {
return {ok: false, error: 'must contain max ' + length + ' characters'};
}
return {ok: true, error: ''};
}
minMax(data, min, max) { // Number between min and max
const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
if (error) {
return {ok: false, error: `must be between ${min} and ${max}`};
}
return {ok: true, error: ''};
}
minMax(data, min, max) { // Number between min and max
const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
if (error) {
return {ok: false, error: `must be between ${min} and ${max}`};
}
return {ok: true, error: ''};
}
min(data, min) { // Number above min
const {ignore, error} = Joi.number().allow('').min(min).validate(data);
if (error) {
return {ok: false, error: `must not be below ${min}`};
}
return {ok: true, error: ''};
}
min(data, min) { // Number above min
const {ignore, error} = Joi.number().allow('').min(min).validate(data);
if (error) {
return {ok: false, error: `must not be below ${min}`};
}
return {ok: true, error: ''};
}
max(data, max) { // Number below max
const {ignore, error} = Joi.number().allow('').max(max).validate(data);
if (error) {
return {ok: false, error: `must not be above ${max}`};
}
return {ok: true, error: ''};
}
max(data, max) { // Number below max
const {ignore, error} = Joi.number().allow('').max(max).validate(data);
if (error) {
return {ok: false, error: `must not be above ${max}`};
}
return {ok: true, error: ''};
}
url(data) {
const {ignore, error} = Joi.string().uri().validate(data);
if (error) {
return {ok: false, error: `must be a valid URL`};
}
return {ok: true, error: ''};
}
url(data) {
const {ignore, error} = Joi.string().uri().validate(data);
if (error) {
return {ok: false, error: `must be a valid URL`};
}
return {ok: true, error: ''};
}
unique(data, list) {
const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: `values must be unique`};
}
return {ok: true, error: ''};
}
unique(data, list) {
const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: `values must be unique`};
}
return {ok: true, error: ''};
}
equal(data, compare) {
if (data !== compare) {
return {ok: false, error: `must be equal`};
}
return {ok: true, error: ''};
}
equal(data, compare) {
if (data !== compare) {
return {ok: false, error: `must be equal`};
}
return {ok: true, error: ''};
}
parameterName(data) {
const {ignore, error} = Joi.string()
.max(128)
.invalid('condition_template', 'material_template')
.allow('')
.pattern(/^[^.]+$/)
.required()
.messages({'string.pattern.base': 'name must not contain a dot'})
.validate(data);
if (error) {
return {ok: false, error: error.details[0].message};
}
return {ok: true, error: ''};
}
parameterName(data) {
const {ignore, error} = Joi.string()
.max(128)
.invalid('condition_template', 'material_template')
.allow('')
.pattern(/^[^.]+$/)
.required()
.messages({'string.pattern.base': 'name must not contain a dot'})
.validate(data);
if (error) {
return {ok: false, error: error.details[0].message};
}
return {ok: true, error: ''};
}
parameterRange(data) {
if (data) {
try {
const {ignore, error} = Joi.object({
values: Joi.array()
.min(1),
parameterRange(data) {
if (data) {
try {
const {ignore, error} = Joi.object({
values: Joi.array()
.min(1),
min: Joi.number(),
min: Joi.number(),
max: Joi.number(),
max: Joi.number(),
type: Joi.string()
.valid('string', 'number', 'boolean', 'array'),
type: Joi.string()
.valid('string', 'number', 'boolean', 'array'),
required: Joi.boolean()
})
.oxor('values', 'min')
.oxor('values', 'max')
.required()
.validate(JSON.parse(data));
if (error) {
return {ok: false, error: error.details[0].message};
}
}
catch (e) {
return {ok: false, error: `no valid JSON`};
}
return {ok: true, error: ''};
}
else {
return {ok: false, error: `no valid value`};
}
}
required: Joi.boolean()
})
.oxor('values', 'min')
.oxor('values', 'max')
.required()
.validate(JSON.parse(data));
if (error) {
return {ok: false, error: error.details[0].message};
}
}
catch (e) {
return {ok: false, error: `no valid JSON`};
}
return {ok: true, error: ''};
}
else {
return {ok: false, error: `no valid value`};
}
}
}

View File

@ -1,46 +1,46 @@
<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-array-input [(ngModel)]="user.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
label="device" appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value"
#deviceInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
</rb-form-input>
</rb-array-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>
<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-array-input [(ngModel)]="user.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
label="device" appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value"
#deviceInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
</rb-form-input>
</rb-array-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>
<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>

View File

@ -1,7 +1,7 @@
.pass-heading {
margin-top: 40px;
margin-top: 40px;
}
.message {
margin-left: 20px;
margin-left: 20px;
}

View File

@ -6,63 +6,63 @@ import {LoginService} from '../services/login.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
user: UserModel = new UserModel(); // User to edit
password = ''; // New password
messageUser = ''; // Messages for user and pass part
messagePass = '';
user: UserModel = new UserModel(); // User to edit
password = ''; // New password
messageUser = ''; // Messages for user and pass part
messagePass = '';
constructor(
private api: ApiService,
private login: LoginService,
private router: Router
) { }
constructor(
private api: ApiService,
private login: LoginService,
private router: Router
) { }
ngOnInit(): void {
this.api.get<UserModel>('/user', data => {
this.user.deserialize(data);
});
}
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 { // Login with new credentials
this.login.login(data.name).then(res => {
if (res) {
this.router.navigate(['/samples']);
}
else {
this.messageUser = 'request not successful, try again';
}
});
}
});
}
saveUser() {
this.api.put<UserModel>('/user', this.user.sendFormat(), (data, err) => {
if (err) {
this.messageUser = err.error.status;
}
else { // Login with new credentials
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 { // Login with new credentials
this.login.login('', this.password).then(res => {
if (res) {
this.router.navigate(['/samples']);
}
else {
this.messagePass = 'request not successful, try again';
}
});
}
});
}
savePass() {
this.api.put<UserModel>('/user', {pass: this.password}, (ignore, err) => {
if (err) {
this.messagePass = err.error.status;
}
else { // Login with new credentials
this.login.login('', this.password).then(res => {
if (res) {
this.router.navigate(['/samples']);
}
else {
this.messagePass = 'request not successful, try again';
}
});
}
});
}
}

View File

@ -1,8 +1,8 @@
import { SizePipe } from './size.pipe';
describe('SizePipe', () => {
it('create an instance', () => {
const pipe = new SizePipe();
expect(pipe).toBeTruthy();
});
it('create an instance', () => {
const pipe = new SizePipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,16 +1,16 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'size'
name: 'size'
})
export class SizePipe implements PipeTransform {
transform(value: number, exp: string): string {
const divide = ['', 'k', 'M', 'G', 'T'].indexOf(exp);
for (let i = 0; i < divide; i ++) {
value = value / 1024;
}
return `${value.toFixed(2)} ${exp}B`;
}
transform(value: number, exp: string): string {
const divide = ['', 'k', 'M', 'G', 'T'].indexOf(exp);
for (let i = 0; i < divide; i ++) {
value = value / 1024;
}
return `${value.toFixed(2)} ${exp}B`;
}
}

View File

@ -1,66 +1,66 @@
<rb-form-select name="collectionSelection" label="collection"
[(ngModel)]="collection" (ngModelChange)="loadTemplates()">
<option value="material">Materials</option>
<option value="measurement">Measurements</option>
<option value="condition">Conditions</option>
[(ngModel)]="collection" (ngModelChange)="loadTemplates()">
<option value="material">Materials</option>
<option value="measurement">Measurements</option>
<option value="condition">Conditions</option>
</rb-form-select>
<rb-icon-button icon="add" mode="primary" (click)="newTemplate()">New template</rb-icon-button>
<div class="list">
<div class="row">
<div class="header">Name</div>
<div class="header">Version</div>
</div>
<div class="row">
<div class="header">Name</div>
<div class="header">Version</div>
</div>
<ng-container *ngFor="let group of groupsView">
<div class="row clickable">
<div (click)="group.expanded = !group.expanded">{{group.name}}</div>
<div (click)="group.expanded = !group.expanded">{{group.version}}</div>
</div>
<div class="row" *ngIf="group.expanded" [@inOut]>
<div class="details">
<ng-container *ngFor="let template of group.entries">
<div>{{template.name}}</div>
<div>{{template.version}}</div>
<div>{{template.parameters | parameters}}</div>
</ng-container>
<div class="template-actions">
<form #templateForm="ngForm">
<div *ngIf="group.edit">
<rb-form-input [name]="'name-' + group.name" label="name" appValidate="string" required
[(ngModel)]="templateEdit[group.first_id].name" #supplierInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-array-input [(ngModel)]="templateEdit[group.first_id].parameters" [name]="'parameters-' + group.name"
[pushTemplate]="{name: '', range: {}, rangeString: '{}'}" pushPath="name"
class="parameters">
<ng-container *rbArrayInputItem="let item">
<rb-form-input [rbArrayInputListener]="'parameter-name-' + group.name" appValidate="parameterName"
[index]="item.i" [name]="'parameter-name-' + group.name + item.i"
label="parameter name" [ngModel]="item.value.name" #parameterName="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterName.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-textarea [name]="'parameter-range-' + group.name + item.i" label="range"
appValidate="parameterRange" [(ngModel)]="item.value.rangeString"
#parameterRange="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterRange.errors.failure}}</ng-template>
</rb-form-textarea>
</ng-container>
</rb-array-input>
</div>
<rb-icon-button icon="edit" mode="secondary" (click)="group.edit = !group.edit">
Edit template
</rb-icon-button>
<rb-icon-button icon="save" mode="primary" (click)="saveTemplate(group.first_id)" *ngIf="group.edit"
[disabled]="templateForm.invalid">
Save template
</rb-icon-button>
</form>
</div>
</div>
</div>
</ng-container>
<ng-container *ngFor="let group of groupsView">
<div class="row clickable">
<div (click)="group.expanded = !group.expanded">{{group.name}}</div>
<div (click)="group.expanded = !group.expanded">{{group.version}}</div>
</div>
<div class="row" *ngIf="group.expanded" [@inOut]>
<div class="details">
<ng-container *ngFor="let template of group.entries">
<div>{{template.name}}</div>
<div>{{template.version}}</div>
<div>{{template.parameters | parameters}}</div>
</ng-container>
<div class="template-actions">
<form #templateForm="ngForm">
<div *ngIf="group.edit">
<rb-form-input [name]="'name-' + group.name" label="name" appValidate="string" required
[(ngModel)]="templateEdit[group.first_id].name" #supplierInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{supplierInput.errors.failure}}</ng-template>
</rb-form-input>
<rb-array-input [(ngModel)]="templateEdit[group.first_id].parameters" [name]="'parameters-' + group.name"
[pushTemplate]="{name: '', range: {}, rangeString: '{}'}" pushPath="name"
class="parameters">
<ng-container *rbArrayInputItem="let item">
<rb-form-input [rbArrayInputListener]="'parameter-name-' + group.name" appValidate="parameterName"
[index]="item.i" [name]="'parameter-name-' + group.name + item.i"
label="parameter name" [ngModel]="item.value.name" #parameterName="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterName.errors.failure}}</ng-template>
</rb-form-input>
<rb-form-textarea [name]="'parameter-range-' + group.name + item.i" label="range"
appValidate="parameterRange" [(ngModel)]="item.value.rangeString"
#parameterRange="ngModel">
<ng-template rbFormValidationMessage="failure">{{parameterRange.errors.failure}}</ng-template>
</rb-form-textarea>
</ng-container>
</rb-array-input>
</div>
<rb-icon-button icon="edit" mode="secondary" (click)="group.edit = !group.edit">
Edit template
</rb-icon-button>
<rb-icon-button icon="save" mode="primary" (click)="saveTemplate(group.first_id)" *ngIf="group.edit"
[disabled]="templateForm.invalid">
Save template
</rb-icon-button>
</form>
</div>
</div>
</div>
</ng-container>
</div>

View File

@ -2,39 +2,39 @@
.list {
.row {
display: grid;
grid-template-columns: 1fr 4fr;
border-bottom: 1px solid $color-gray-mercury;
overflow: hidden;
.row {
display: grid;
grid-template-columns: 1fr 4fr;
border-bottom: 1px solid $color-gray-mercury;
overflow: hidden;
& > div {
padding: 8px 5px;
& > div {
padding: 8px 5px;
&.header {
font-weight: bold;
}
&.header {
font-weight: bold;
}
&.details {
grid-column: span 2;
display: grid;
grid-template-columns: 1fr 1fr 3fr;
background: $color-gray-alabaster;
&.details {
grid-column: span 2;
display: grid;
grid-template-columns: 1fr 1fr 3fr;
background: $color-gray-alabaster;
.template-actions {
grid-column: span 3;
margin-top: 10px;
.template-actions {
grid-column: span 3;
margin-top: 10px;
.parameters {
display: grid;
grid-template-columns: 1fr 2fr;
}
.parameters {
display: grid;
grid-template-columns: 1fr 2fr;
}
rb-icon-button[icon="save"] {
float: right;
}
}
}
}
}
rb-icon-button[icon="save"] {
float: right;
}
}
}
}
}
}

View File

@ -8,132 +8,132 @@ import omit from 'lodash/omit';
import {DataService} from '../services/data.service';
@Component({
selector: 'app-templates',
templateUrl: './templates.component.html',
styleUrls: ['./templates.component.scss'],
animations: [
trigger(
'inOut', [
transition(':enter', [
style({height: 0, opacity: 0}),
animate('0.5s ease-out', style({height: '*', opacity: 1}))
]),
transition(':leave', [
style({height: '*', opacity: 1}),
animate('0.5s ease-in', style({height: 0, opacity: 0}))
])
]
)
]
selector: 'app-templates',
templateUrl: './templates.component.html',
styleUrls: ['./templates.component.scss'],
animations: [
trigger(
'inOut', [
transition(':enter', [
style({height: 0, opacity: 0}),
animate('0.5s ease-out', style({height: '*', opacity: 1}))
]),
transition(':leave', [
style({height: '*', opacity: 1}),
animate('0.5s ease-in', style({height: 0, opacity: 0}))
])
]
)
]
})
export class TemplatesComponent implements OnInit {
collection = 'measurement'; // Collection to view
templates: TemplateModel[] = []; // All templates of current collection
templateGroups: {[first_id: string]: TemplateModel[]} = {}; // Templates grouped by first_id
templateEdit: {[first_id: string]: TemplateModel} = {}; // Latest template of each first_id for editing
groupsView: { // Grouped templates
first_id: string,
name: string,
version: number,
expanded: boolean,
edit: boolean,
entries: TemplateModel[]
}[] = [];
collection = 'measurement'; // Collection to view
templates: TemplateModel[] = []; // All templates of current collection
templateGroups: {[first_id: string]: TemplateModel[]} = {}; // Templates grouped by first_id
templateEdit: {[first_id: string]: TemplateModel} = {}; // Latest template of each first_id for editing
groupsView: { // Grouped templates
first_id: string,
name: string,
version: number,
expanded: boolean,
edit: boolean,
entries: TemplateModel[]
}[] = [];
constructor(
private api: ApiService,
private validate: ValidationService,
public d: DataService
) { }
constructor(
private api: ApiService,
private validate: ValidationService,
public d: DataService
) { }
ngOnInit(): void {
this.loadTemplates();
}
ngOnInit(): void {
this.loadTemplates();
}
loadTemplates() {
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
}
loadTemplates() {
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
}
templateFormat() {
this.templateGroups = {};
this.templateEdit = {};
this.templates.forEach(template => { // Group templates
if (this.templateGroups[template.first_id]) {
this.templateGroups[template.first_id].push(template);
}
else {
this.templateGroups[template.first_id] = [template];
}
});
Object.keys(this.templateGroups).forEach(id => {
this.templateGroups[id] = this.templateGroups[id].sort((a, b) => a.version - b.version);
this.templateEdit[id] = cloneDeep(this.templateGroups[id][this.templateGroups[id].length - 1]);
this.templateEdit[id].parameters = this.templateEdit[id].parameters
.map(e => {e.rangeString = JSON.stringify(e.range, null, 2); return e; });
});
this.groupsView = Object.values(this.templateGroups)
.map(e => ({
first_id: e[e.length - 1].first_id,
name: e[e.length - 1].name,
version: e[e.length - 1].version,
expanded: false,
edit: false,
entries: e
}));
}
templateFormat() {
this.templateGroups = {};
this.templateEdit = {};
this.templates.forEach(template => { // Group templates
if (this.templateGroups[template.first_id]) {
this.templateGroups[template.first_id].push(template);
}
else {
this.templateGroups[template.first_id] = [template];
}
});
Object.keys(this.templateGroups).forEach(id => {
this.templateGroups[id] = this.templateGroups[id].sort((a, b) => a.version - b.version);
this.templateEdit[id] = cloneDeep(this.templateGroups[id][this.templateGroups[id].length - 1]);
this.templateEdit[id].parameters = this.templateEdit[id].parameters
.map(e => {e.rangeString = JSON.stringify(e.range, null, 2); return e; });
});
this.groupsView = Object.values(this.templateGroups)
.map(e => ({
first_id: e[e.length - 1].first_id,
name: e[e.length - 1].name,
version: e[e.length - 1].version,
expanded: false,
edit: false,
entries: e
}));
}
saveTemplate(first_id) {
const template = cloneDeep(this.templateEdit[first_id]);
template.parameters = template.parameters.filter(e => e.name !== '');
let valid = true;
valid = valid && this.validate.string(template.name).ok;
template.parameters.forEach(parameter => {
valid = valid && this.validate.parameterName(parameter.name).ok;
valid = valid && this.validate.parameterRange(parameter.rangeString).ok;
if (valid) {
parameter.range = JSON.parse(parameter.rangeString);
}
});
if (valid) {
const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))};
if (first_id === 'null') { // New template
this.api.post<TemplateModel>(`/template/${this.collection}/new`, sendData, () => {
delete this.d.arr[this.collection + 'Templates'];
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
});
}
else {
this.api.put<TemplateModel>(`/template/${this.collection}/${template.first_id}`, sendData, () => {
delete this.d.arr[this.collection + 'Templates'];
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
});
}
}
}
saveTemplate(first_id) {
const template = cloneDeep(this.templateEdit[first_id]);
template.parameters = template.parameters.filter(e => e.name !== '');
let valid = true;
valid = valid && this.validate.string(template.name).ok;
template.parameters.forEach(parameter => {
valid = valid && this.validate.parameterName(parameter.name).ok;
valid = valid && this.validate.parameterRange(parameter.rangeString).ok;
if (valid) {
parameter.range = JSON.parse(parameter.rangeString);
}
});
if (valid) {
const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))};
if (first_id === 'null') { // New template
this.api.post<TemplateModel>(`/template/${this.collection}/new`, sendData, () => {
delete this.d.arr[this.collection + 'Templates'];
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
});
}
else {
this.api.put<TemplateModel>(`/template/${this.collection}/${template.first_id}`, sendData, () => {
delete this.d.arr[this.collection + 'Templates'];
this.d.load(this.collection + 'Templates', () => {
this.templates = this.d.arr[this.collection + 'Templates'];
this.templateFormat();
});
});
}
}
}
newTemplate() {
if (!this.templateEdit.null) {
const template = new TemplateModel();
template.name = 'new template';
this.groupsView.push({
first_id: 'null',
name: 'new template',
version: 0,
expanded: true,
edit: true,
entries: [template]
});
this.templateEdit.null = new TemplateModel();
}
}
newTemplate() {
if (!this.templateEdit.null) {
const template = new TemplateModel();
template.name = 'new template';
this.groupsView.push({
first_id: 'null',
name: 'new template',
version: 0,
expanded: true,
edit: true,
entries: [template]
});
this.templateEdit.null = new TemplateModel();
}
}
}

View File

@ -1,170 +1,170 @@
<rb-icon-button icon="add" mode="primary" (click)="addNewUser()" class="space-below">New user</rb-icon-button>
<form *ngIf="newUser" #userForm="ngForm" class="space-below">
<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 (Abbreviation, eg. Rng, for sample number)" appValidate="string"
[appValidateArgs]="['alphanum']" required
[(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-array-input [(ngModel)]="newUser.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
label="device" appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value"
#deviceInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
</rb-form-input>
</rb-array-input>
<rb-array-input [(ngModel)]="newUser.models" name="models" [pushTemplate]="''">
<rb-form-select *rbArrayInputItem="let item" rbArrayInputListener="models" [index]="item.i" label="model"
[name]="'model-' + item.i" [ngModel]="item.value">
<ng-container *ngTemplateOutlet="modelOptions"></ng-container>
</rb-form-select>
</rb-array-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>
<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 (Abbreviation, eg. Rng, for sample number)" appValidate="string"
[appValidateArgs]="['alphanum']" required
[(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-array-input [(ngModel)]="newUser.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
label="device" appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value"
#deviceInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{deviceInput.errors.failure}}</ng-template>
</rb-form-input>
</rb-array-input>
<rb-array-input [(ngModel)]="newUser.models" name="models" [pushTemplate]="''">
<rb-form-select *rbArrayInputItem="let item" rbArrayInputListener="models" [index]="item.i" label="model"
[name]="'model-' + item.i" [ngModel]="item.value">
<ng-container *ngTemplateOutlet="modelOptions"></ng-container>
</rb-form-select>
</rb-array-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 scrollTop>
<tr>
<th>Name</th>
<th>Email</th>
<th>Level</th>
<th>Location</th>
<th>Device</th>
<th>Models</th>
<th></th>
</tr>
<tr>
<th>Name</th>
<th>Email</th>
<th>Level</th>
<th>Location</th>
<th>Device</th>
<th>Models</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.devices}}</td>
<td>
<ng-container *ngFor="let model of user.models; index as i">
{{(i > 0 ? ', ' : '') + modelIds[model]}}
</ng-container>
</td>
<td><span class="rb-ic clickable rb-ic-edit" (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-array-input [(ngModel)]="user.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value">
</rb-form-input>
</rb-array-input>
</td>
<td>
<rb-array-input [(ngModel)]="user.models" name="devices" [pushTemplate]="''">
<rb-form-select *rbArrayInputItem="let item" rbArrayInputListener="models" [index]="item.i"
[name]="'model-' + item.i" [ngModel]="item.value">
<ng-container *ngTemplateOutlet="modelOptions"></ng-container>
</rb-form-select>
</rb-array-input>
</td>
<td>
<rb-icon-button icon="delete" mode="danger" class="space-below"
(click)="deleteConfirm(modalDeleteConfirm, user)">
Delete
</rb-icon-button>
<ng-template #modalDeleteConfirm>
<rb-alert alertTitle="Are you sure?" type="danger" okBtnLabel="Delete user" cancelBtnLabel="Cancel">
Do you really want to delete this user?
</rb-alert>
</ng-template>
<rb-icon-button icon="save" mode="primary" (click)="saveUser(user)"
[disabled]="nameInput.invalid || emailInput.invalid || locationInput.invalid">
Save
</rb-icon-button>
</td>
</ng-template>
</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.devices}}</td>
<td>
<ng-container *ngFor="let model of user.models; index as i">
{{(i > 0 ? ', ' : '') + modelIds[model]}}
</ng-container>
</td>
<td><span class="rb-ic clickable rb-ic-edit" (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-array-input [(ngModel)]="user.devices" name="devices" [pushTemplate]="''">
<rb-form-input *rbArrayInputItem="let item" rbArrayInputListener="devices" [index]="item.i"
appValidate="string" [name]="'device-' + item.i" [ngModel]="item.value">
</rb-form-input>
</rb-array-input>
</td>
<td>
<rb-array-input [(ngModel)]="user.models" name="devices" [pushTemplate]="''">
<rb-form-select *rbArrayInputItem="let item" rbArrayInputListener="models" [index]="item.i"
[name]="'model-' + item.i" [ngModel]="item.value">
<ng-container *ngTemplateOutlet="modelOptions"></ng-container>
</rb-form-select>
</rb-array-input>
</td>
<td>
<rb-icon-button icon="delete" mode="danger" class="space-below"
(click)="deleteConfirm(modalDeleteConfirm, user)">
Delete
</rb-icon-button>
<ng-template #modalDeleteConfirm>
<rb-alert alertTitle="Are you sure?" type="danger" okBtnLabel="Delete user" cancelBtnLabel="Cancel">
Do you really want to delete this user?
</rb-alert>
</ng-template>
<rb-icon-button icon="save" mode="primary" (click)="saveUser(user)"
[disabled]="nameInput.invalid || emailInput.invalid || locationInput.invalid">
Save
</rb-icon-button>
</td>
</ng-template>
</tr>
</rb-table>
<rb-accordion>
<rb-accordion-title [open]="false"><span class="rb-ic rb-ic-delete"></span>&nbsp; Deleted users</rb-accordion-title>
<rb-accordion-body>
<rb-table scrollTop>
<tr>
<th>Name</th>
<th>Email</th>
<th>Level</th>
<th>Location</th>
<th>Device</th>
<th>Models</th>
<th></th>
</tr>
<rb-accordion-title [open]="false"><span class="rb-ic rb-ic-delete"></span>&nbsp; Deleted users</rb-accordion-title>
<rb-accordion-body>
<rb-table scrollTop>
<tr>
<th>Name</th>
<th>Email</th>
<th>Level</th>
<th>Location</th>
<th>Device</th>
<th>Models</th>
<th></th>
</tr>
<tr *ngFor="let user of deletedUsers">
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.level}}</td>
<td>{{user.location}}</td>
<td>{{user.devices}}</td>
<td>
<ng-container *ngFor="let model of user.models; index as i">
{{(i > 0 ? ', ' : '') + modelIds[model]}}
</ng-container>
</td>
<td><span class="rb-ic clickable rb-ic-undo" (click)="restoreUser(user)"></span></td>
</tr>
</rb-table>
</rb-accordion-body>
<tr *ngFor="let user of deletedUsers">
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.level}}</td>
<td>{{user.location}}</td>
<td>{{user.devices}}</td>
<td>
<ng-container *ngFor="let model of user.models; index as i">
{{(i > 0 ? ', ' : '') + modelIds[model]}}
</ng-container>
</td>
<td><span class="rb-ic clickable rb-ic-undo" (click)="restoreUser(user)"></span></td>
</tr>
</rb-table>
</rb-accordion-body>
</rb-accordion>
<ng-template #modelOptions>
<option value=""></option>
<ng-container *ngFor="let model of modelSelect">
<option [value]="model.id">{{model.name}}</option>
</ng-container>
<option value=""></option>
<ng-container *ngFor="let model of modelSelect">
<option [value]="model.id">{{model.name}}</option>
</ng-container>
</ng-template>

View File

@ -1,16 +1,16 @@
::ng-deep td .error-messages {
position: absolute;
position: absolute;
}
td:last-child rb-icon-button {
width: 100px;
float: left;
width: 100px;
float: left;
::ng-deep button {
width: 100%;
}
::ng-deep button {
width: 100%;
}
}
rb-form-select {
min-width: 150px;
min-width: 150px;
}

View File

@ -7,78 +7,78 @@ import {DataService} from '../services/data.service';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.scss']
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.scss']
})
export class UsersComponent implements OnInit {
users: UserModel[] = []; // All active users
deletedUsers: UserModel[] = []; // All deleted users
newUser: UserModel | null = null; // Data of new user
newUserPass = ''; // Password of new user
modelSelect: {id: string, name: string}[] = []; // List of all models for selection
modelIds: {[id: string]: string} = {}; // All models by id
users: UserModel[] = []; // All active users
deletedUsers: UserModel[] = []; // All deleted users
newUser: UserModel | null = null; // Data of new user
newUserPass = ''; // Password of new user
modelSelect: {id: string, name: string}[] = []; // List of all models for selection
modelIds: {[id: string]: string} = {}; // All models by id
constructor(
private api: ApiService,
public login: LoginService,
private modal: ModalService,
public d: DataService
) { }
constructor(
private api: ApiService,
public login: LoginService,
private modal: ModalService,
public d: DataService
) { }
ngOnInit(): void {
this.api.get<UserModel[]>('/users', data => {
this.users = data.map(e => new UserModel().deserialize(e)).filter(e => e.status !== 'deleted');
this.deletedUsers = data.map(e => new UserModel().deserialize(e)).filter(e => e.status === 'deleted');
});
this.d.load('modelGroups', () => {
this.d.arr.modelGroups.forEach(group => {
this.modelSelect.push(...group.models.map(e => ({id: e._id, name: `${group.group} - ${e.name}`})));
});
this.modelIds = this.modelSelect.reduce((s, e) => {s[e.id] = e.name; return s; }, {});
});
}
ngOnInit(): void {
this.api.get<UserModel[]>('/users', data => {
this.users = data.map(e => new UserModel().deserialize(e)).filter(e => e.status !== 'deleted');
this.deletedUsers = data.map(e => new UserModel().deserialize(e)).filter(e => e.status === 'deleted');
});
this.d.load('modelGroups', () => {
this.d.arr.modelGroups.forEach(group => {
this.modelSelect.push(...group.models.map(e => ({id: e._id, name: `${group.group} - ${e.name}`})));
});
this.modelIds = this.modelSelect.reduce((s, e) => {s[e.id] = e.name; return s; }, {});
});
}
saveUser(user: UserModel) {
user.models = user.models.filter(e => e !== '');
this.api.put<UserModel>('/user/' + user.origName, user.sendFormat('admin'), data => {
user.deserialize(data);
user.edit = false;
});
}
saveUser(user: UserModel) {
user.models = user.models.filter(e => e !== '');
this.api.put<UserModel>('/user/' + user.origName, user.sendFormat('admin'), data => {
user.deserialize(data);
user.edit = false;
});
}
saveNewUser() {
this.newUser.models = this.newUser.models.filter(e => e !== '');
this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => {
this.newUser = null;
this.users.push(new UserModel().deserialize(data));
this.newUserPass = '';
});
}
saveNewUser() {
this.newUser.models = this.newUser.models.filter(e => e !== '');
this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => {
this.newUser = null;
this.users.push(new UserModel().deserialize(data));
this.newUserPass = '';
});
}
addNewUser() {
this.newUser = this.newUser ? null : new UserModel();
}
addNewUser() {
this.newUser = this.newUser ? null : new UserModel();
}
deleteConfirm(modal, user) {
this.modal.open(modal).then(result => {
if (result) {
this.api.delete('/user/' + user.name, () => {
user.status = 'deleted';
user.edit = false;
this.deletedUsers.push(user);
this.users.splice(this.users.findIndex(e => e.name === user.name), 1);
});
}
});
}
deleteConfirm(modal, user) {
this.modal.open(modal).then(result => {
if (result) {
this.api.delete('/user/' + user.name, () => {
user.status = 'deleted';
user.edit = false;
this.deletedUsers.push(user);
this.users.splice(this.users.findIndex(e => e.name === user.name), 1);
});
}
});
}
restoreUser(user) {
this.api.put('/user/restore/' + user.name, {}, () => {
user.status = 'new';
this.users.push(user);
this.deletedUsers.splice(this.deletedUsers.findIndex(e => e.name === user.name), 1);
});
}
restoreUser(user) {
this.api.put('/user/restore/' + user.name, {}, () => {
user.status = 'new';
this.users.push(user);
this.deletedUsers.splice(this.deletedUsers.findIndex(e => e.name === user.name), 1);
});
}
}

View File

@ -3,26 +3,26 @@ import {AbstractControl, NG_VALIDATORS} from '@angular/forms';
import {ValidationService} from './services/validation.service';
@Directive({
selector: '[appValidate]',
providers: [{provide: NG_VALIDATORS, useExisting: ValidateDirective, multi: true}]
selector: '[appValidate]',
providers: [{provide: NG_VALIDATORS, useExisting: ValidateDirective, multi: true}]
})
export class ValidateDirective {
@Input('appValidate') method: string;
@Input('appValidateArgs') args: Array<any>;
@Input('appValidate') method: string;
@Input('appValidateArgs') args: Array<any>;
constructor(
private validation: ValidationService
) { }
constructor(
private validation: ValidationService
) { }
validate(control: AbstractControl): {[key: string]: any} | null {
let ok;
let error;
if (this.args) {
({ok, error} = this.validation[this.method](control.value, ...this.args));
}
else {
({ok, error} = this.validation[this.method](control.value));
}
return ok ? null : { failure: error };
}
validate(control: AbstractControl): {[key: string]: any} | null {
let ok;
let error;
if (this.args) {
({ok, error} = this.validation[this.method](control.value, ...this.args));
}
else {
({ok, error} = this.validation[this.method](control.value));
}
return ok ? null : { failure: error };
}
}

View File

@ -1,3 +1,3 @@
export const environment = {
production: true
production: true
};

View File

@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false
};
/*