Properly indent all source files
This commit is contained in:
parent
2d3782cc82
commit
2f6231a6b5
@ -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 { }
|
||||
|
@ -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> 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> 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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 { }
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(() => {});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 <key name, can be whatever></li>
|
||||
<li>cf login</li>
|
||||
<li>cf enable-ssh definma-api</li>
|
||||
<li>cf create-service-key definmadb <key name, can be whatever></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 > DeFinMa > production > definma-api > Services > dots on the right of definmadb > 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 > DeFinMa > production > definma-api > Services > dots on the right of definmadb > 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><collection>_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><collection>_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><binary data></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><binary data></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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 "python -m http.server" 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
|
||||
"Disable cache" 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 "python -m http.server" 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
|
||||
"Disable cache" 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>
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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) : '';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,7 +1,7 @@
|
||||
.delete-btn {
|
||||
float: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
white-space: pre-line;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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!';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
.delete-material {
|
||||
float: right;
|
||||
float: right;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,6 @@ import {BaseModel} from './base.model';
|
||||
|
||||
|
||||
export class CustomFieldsModel extends BaseModel {
|
||||
name = '';
|
||||
qty = 0;
|
||||
name = '';
|
||||
qty = 0;
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {BaseModel} from './base.model';
|
||||
|
||||
export class HelpModel extends BaseModel {
|
||||
text = '';
|
||||
level = 'none';
|
||||
text = '';
|
||||
level = 'none';
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {BaseModel} from './base.model';
|
||||
|
||||
export class ModelFileModel extends BaseModel {
|
||||
name = '';
|
||||
size = 0;
|
||||
name = '';
|
||||
size = 0;
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {BaseModel} from './base.model';
|
||||
|
||||
export class ModelItemModel extends BaseModel {
|
||||
group = '';
|
||||
models = [{
|
||||
name: '',
|
||||
url: ''
|
||||
}];
|
||||
group = '';
|
||||
models = [{
|
||||
name: '',
|
||||
url: ''
|
||||
}];
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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}[] = [];
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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) : '';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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(', ')}}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{predictionEntry.label}} {{( 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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{predictionEntry.label}} {{( 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}} <span [ngStyle]="{color: predictionEntry.color}">{{predictionEntry.value}}</span> {{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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 { }
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
@ -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> 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> 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">≠</option>
|
||||
<option value="lt" title="field is lower than value"><</option>
|
||||
<option value="lte" title="field is lower than or equal to value">≤</option>
|
||||
<option value="gt" title="field is greater than value">></option>
|
||||
<option value="gte" title="field is greater than or equal to value">≥</option>
|
||||
<option value="stringin" title="field contains value">⊇</option>
|
||||
<option value="in" title="field is one of the values">∈</option>
|
||||
<option value="nin" title="field is not one of the values">∉</option>
|
||||
<option value="null" title="field is null">∅</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">≠</option>
|
||||
<option value="lt" title="field is lower than value"><</option>
|
||||
<option value="lte" title="field is lower than or equal to value">≤</option>
|
||||
<option value="gt" title="field is greater than value">></option>
|
||||
<option value="gte" title="field is greater than or equal to value">≥</option>
|
||||
<option value="stringin" title="field contains value">⊇</option>
|
||||
<option value="in" title="field is one of the values">∈</option>
|
||||
<option value="nin" title="field is not one of the values">∉</option>
|
||||
<option value="null" title="field is null">∅</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>
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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`};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
.pass-heading {
|
||||
margin-top: 40px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user