Merge pull request #35 in ~VLE2FE/definma-ui from development to master

* commit 'cd4e2aa77b5dd66d4898adf7494e47a0466a9595':
  added model documentation
  added material search
  fixed new user model input and renaming model group
  renamed material name to product name, removed headings
This commit is contained in:
Veit Lukas (PEA4-Fe) 2020-09-01 15:15:54 +02:00
commit 1f1ce7dd9c
29 changed files with 163 additions and 31 deletions

View File

@ -15,6 +15,7 @@ import {ModelTemplatesComponent} from './model-templates/model-templates.compone
import {DocumentationArchitectureComponent} from './documentation/documentation-architecture/documentation-architecture.component'; import {DocumentationArchitectureComponent} from './documentation/documentation-architecture/documentation-architecture.component';
import {MaterialsComponent} from './materials/materials.component'; import {MaterialsComponent} from './materials/materials.component';
import {MaterialComponent} from './material/material.component'; import {MaterialComponent} from './material/material.component';
import {DocumentationModelsComponent} from './documentation/documentation-models/documentation-models.component';
const routes: Routes = [ const routes: Routes = [
@ -34,6 +35,7 @@ const routes: Routes = [
{path: 'documentation', component: DocumentationComponent}, {path: 'documentation', component: DocumentationComponent},
{path: 'documentation/architecture', component: DocumentationArchitectureComponent}, {path: 'documentation/architecture', component: DocumentationArchitectureComponent},
{path: 'documentation/database', component: DocumentationDatabaseComponent}, {path: 'documentation/database', component: DocumentationDatabaseComponent},
{path: 'documentation/models', component: DocumentationModelsComponent},
// if not authenticated // if not authenticated
{ path: '**', redirectTo: '' } { path: '**', redirectTo: '' }

View File

@ -18,6 +18,7 @@
<a routerLink="/documentation" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">General</a> <a routerLink="/documentation" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">General</a>
<a routerLink="/documentation/architecture" routerLinkActive="active">Architecture</a> <a routerLink="/documentation/architecture" routerLinkActive="active">Architecture</a>
<a routerLink="/documentation/database" routerLinkActive="active">Database</a> <a routerLink="/documentation/database" routerLinkActive="active">Database</a>
<a routerLink="/documentation/models" routerLinkActive="active">Models</a>
</nav> </nav>
</ng-container> </ng-container>

View File

@ -34,6 +34,7 @@ import { SizePipe } from './size.pipe';
import { DocumentationArchitectureComponent } from './documentation/documentation-architecture/documentation-architecture.component'; import { DocumentationArchitectureComponent } from './documentation/documentation-architecture/documentation-architecture.component';
import { MaterialsComponent } from './materials/materials.component'; import { MaterialsComponent } from './materials/materials.component';
import { MaterialComponent } from './material/material.component'; import { MaterialComponent } from './material/material.component';
import { DocumentationModelsComponent } from './documentation/documentation-models/documentation-models.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -60,7 +61,8 @@ import { MaterialComponent } from './material/material.component';
SizePipe, SizePipe,
DocumentationArchitectureComponent, DocumentationArchitectureComponent,
MaterialsComponent, MaterialsComponent,
MaterialComponent MaterialComponent,
DocumentationModelsComponent
], ],
imports: [ imports: [
LocalStorageModule.forRoot({ LocalStorageModule.forRoot({

View File

@ -1,4 +1,3 @@
<h2>Changelog</h2>
<div class="header"> <div class="header">
<rb-form-date-input name="dateInput" label="older than" [options]="{enableTime: true}" <rb-form-date-input name="dateInput" label="older than" [options]="{enableTime: true}"

View File

@ -1,4 +1,3 @@
<h4>Architecture</h4>
<img src="/assets/imgs/architecture.svg" alt="architecture" class="space-below"> <img src="/assets/imgs/architecture.svg" alt="architecture" class="space-below">
@ -12,8 +11,8 @@
https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/ https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/
</a><br> </a><br>
Admin database management page: Admin database management page:
<a href="https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/"> <a href="https://definma-db.apps.de1.bosch-iot-cloud.com/">
https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/ https://definma-db.apps.de1.bosch-iot-cloud.com/
</a><br> </a><br>
Code repository UI Code repository UI
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui"> <a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui">
@ -23,6 +22,10 @@
<a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api"> <a href="https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api">
https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api
</a><br> </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>
<p> <p>

View File

@ -1,4 +1,3 @@
<h2>Database</h2>
<p> <p>
The used database instance is a MongoDB instance running on the BIC, storing all application data. The admin database The used database instance is a MongoDB instance running on the BIC, storing all application data. The admin database
@ -31,11 +30,12 @@ Every time:
<h6>Backup</h6> <h6>Backup</h6>
<p> <p>
For creating a database backup, you must follow the same steps from above (except the last one). Additionally you need 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/community">MongoDB server</a> installed. Open the MongoDB bin folder the <a href="https://docs.mongodb.com/database-tools/installation/#install-tools">mongodump</a> installed.
Open the MongoDB bin folder
(normally at C:\Program Files\MongoDB\Server\4.2\bin) in a PowerShell and execute following command for backup, (normally at C:\Program Files\MongoDB\Server\4.2\bin) in a PowerShell and execute following command for backup,
adjust parameters and credentials as needed: <br><br> adjust parameters and credentials as needed: <br><br>
mongodump.exe /port:1120 /db:&quot;6ebe4c5d-0da3-4347-b484-66894dcf3f27&quot; /username:&quot;&lt:username&gt;&quot; .\mongodump.exe /port:1120 /db:&quot;6ebe4c5d-0da3-4347-b484-66894dcf3f27&quot; /username:&quot;&lt;username&gt;&quot;
/password:&quot;&lt:username&gt;&quot; /out:&quot;C:\Path\to\backup\folder&quot;<br><br> /password:&quot;&lt;username&gt;&quot; /out:&quot;C:\Path\to\backup\folder&quot;<br><br>
Restoring the database from a backup is done with mongorestore.exe, more information can be found at the Restoring the database from a backup is done with mongorestore.exe, more information can be found at the
<a href="https://docs.mongodb.com/database-tools/">documentation</a>. <br> <a href="https://docs.mongodb.com/database-tools/">documentation</a>. <br>
The BIC service also creates internal backup, which can be requested to restore, see The BIC service also creates internal backup, which can be requested to restore, see

View File

@ -0,0 +1,56 @@
<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.
</p>
<h4 class="space-above">Adding new model scripts</h4>
<p>
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>
</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.
</p>
<ul>
<li>Open a separate Anaconda Prompt and cd into the definma-UI folder</li>
<li>Adapt the model-mock.json at definma-UI/assets/ and enter the new model URL and name</li>
<li>Execute &quot;python -m http.server&quot; in the Anaconda Prompt</li>
<li>Execute the model.py script in Spyder</li>
<li>Navigate to <a href="http://localhost:8000">http://localhost:8000</a> in the browser</li>
<li>If there are problems with cached data, open the Browser developer console (Ctrl+Shift+I) and tick
&quot;Disable cache&quot; in the Network tab. The console then has to stay open.</li>
</ul>
<h4 class="space-above">Model script upload</h4>
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>
</ul>
<p>
After upload the new model details have to be entered in the UI.
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentationModelsComponent } from './documentation-models.component';
describe('DocumentationModelsComponent', () => {
let component: DocumentationModelsComponent;
let fixture: ComponentFixture<DocumentationModelsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DocumentationModelsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DocumentationModelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@ -1,4 +1,3 @@
<h2>Documentation</h2>
<p> <p>
<a [href]="api.hostName + '/static/intro-presentation/index.html'"> <a [href]="api.hostName + '/static/intro-presentation/index.html'">

View File

@ -1,7 +1,7 @@
<h2>Edit material</h2> <h2>{{material.name | exists}}</h2>
<form #materialForm="ngForm" *ngIf="!loading"> <form #materialForm="ngForm" *ngIf="!loading">
<rb-form-input name="materialname" label="material name" appValidate="stringNin" [appValidateArgs]="[materialNames]" <rb-form-input name="materialname" label="product name" appValidate="stringNin" [appValidateArgs]="[materialNames]"
required [(ngModel)]="material.name" #materialnameInput="ngModel"> required [(ngModel)]="material.name" #materialnameInput="ngModel">
<ng-template rbFormValidationMessage="failure">{{materialnameInput.errors.failure}}</ng-template> <ng-template rbFormValidationMessage="failure">{{materialnameInput.errors.failure}}</ng-template>
</rb-form-input> </rb-form-input>

View File

@ -1,5 +1,4 @@
<div class="header-addnew"> <div class="header-addnew">
<h2>Materials</h2>
<rb-icon-button *ngIf="sampleSelect" mode="secondary" icon="close" (click)="sampleSelect = false" <rb-icon-button *ngIf="sampleSelect" mode="secondary" icon="close" (click)="sampleSelect = false"
class="validation-close" iconOnly></rb-icon-button> class="validation-close" iconOnly></rb-icon-button>
<rb-icon-button [icon]="sampleSelect ? 'checkmark' : 'clear-all'" <rb-icon-button [icon]="sampleSelect ? 'checkmark' : 'clear-all'"
@ -27,6 +26,11 @@
</rb-form-checkbox> </rb-form-checkbox>
</div> </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>
</div>
<ng-container *ngTemplateOutlet="paging"></ng-container> <ng-container *ngTemplateOutlet="paging"></ng-container>
<rb-table ellipsis scrollTop> <rb-table ellipsis scrollTop>
@ -41,7 +45,8 @@
<th>Numbers</th> <th>Numbers</th>
<th></th> <th></th>
</tr> </tr>
<tr *ngFor="let material of (materials || []).slice((page - 1) * pageSize, page * pageSize); index as i"> <tr *ngFor="let material of (materials || []).filter(materialFilter(materialSearch))
.slice((page - 1) * pageSize, page * pageSize); index as i">
<td *ngIf="sampleSelect"> <td *ngIf="sampleSelect">
<rb-form-checkbox *ngIf="material.status !== 'deleted'" [name]="'validate-' + i" <rb-form-checkbox *ngIf="material.status !== 'deleted'" [name]="'validate-' + i"
[(ngModel)]="material.selected"> [(ngModel)]="material.selected">

View File

@ -40,8 +40,6 @@
} }
.header-addnew { .header-addnew {
margin-bottom: 40px;
& > * { & > * {
display: inline; display: inline;
margin-bottom: 10px; margin-bottom: 10px;
@ -51,3 +49,15 @@
float: right; float: right;
} }
} }
.material-search {
width: 300px;
float: left;
position: relative;
span {
position: absolute;
right: 5px;
top: 24px;
}
}

View File

@ -15,6 +15,7 @@ export class MaterialsComponent implements OnInit {
materials: MaterialModel[] = []; materials: MaterialModel[] = [];
templateKeys: {key: string, label: string}[] = []; templateKeys: {key: string, label: string}[] = [];
materialStatus = {validated: true, new: true, deleted: false}; materialStatus = {validated: true, new: true, deleted: false};
materialSearch = '';
sampleSelect = false; sampleSelect = false;
page = 1; page = 1;
@ -89,4 +90,8 @@ export class MaterialsComponent implements OnInit {
return string[0].toUpperCase() + string.slice(1); return string[0].toUpperCase() + string.slice(1);
} }
materialFilter(ms) {
return e => e.name.indexOf(ms) >= 0;
}
} }

View File

@ -1,4 +1,3 @@
<h2>Models</h2>
<rb-icon-button icon="add" mode="primary" (click)="newModel = !newModel; oldModelGroup = ''" class="space-below"> <rb-icon-button icon="add" mode="primary" (click)="newModel = !newModel; oldModelGroup = ''" class="space-below">
New model New model

View File

@ -43,7 +43,7 @@ export class ModelTemplatesComponent implements OnInit {
console.log(this.modelGroup); console.log(this.modelGroup);
console.log(this.oldModelGroup); console.log(this.oldModelGroup);
if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) { // group was changed, delete model in old group if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) { // group was changed, delete model in old group
this.delete(null, this.oldModelGroup, this.oldModelName); this.delete(null, this.oldModelName, this.oldModelGroup);
} }
this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => { this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => {
this.newModel = false; this.newModel = false;

View File

@ -25,6 +25,8 @@ export class UserModel extends BaseModel{
if (mode === 'admin') { if (mode === 'admin') {
keys.push('level'); keys.push('level');
keys.push('models'); keys.push('models');
this.devices = this.devices.filter(e => e);
this.models = this.models.filter(e => e);
} }
return pick(this, keys); return pick(this, keys);
} }

View File

@ -1,4 +1,3 @@
<h2>Prediction</h2>
<rb-tab-panel (tabChanged)="groupChange($event)"> <rb-tab-panel (tabChanged)="groupChange($event)">
<ng-container *ngFor="let group of d.arr.modelGroups; index as i"> <ng-container *ngFor="let group of d.arr.modelGroups; index as i">

View File

@ -122,16 +122,22 @@ export class RbArrayInputComponent implements ControlValueAccessor, OnInit, Afte
this.values = [this.values[0]]; this.values = [this.values[0]];
res = this.values; res = this.values;
} }
if (!res.length) { // if (!res.length) {
res = ['']; // res = [''];
} // }
this.onChange(res); // trigger ngModel with filled elements this.onChange(res); // trigger ngModel with filled elements
} }
writeValue(obj: any) { // add empty value on init writeValue(obj: any) { // add empty value on init
console.log(obj);
if (obj) { if (obj) {
if (this.pushTemplate !== null) { if (this.pushTemplate !== null) {
this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)]; 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 { else {
this.values = obj; this.values = obj;

View File

@ -9,7 +9,7 @@
<form #sampleForm="ngForm" *ngIf="view.base"> <form #sampleForm="ngForm" *ngIf="view.base">
<div class="sample"> <div class="sample">
<div> <div>
<rb-form-input name="materialname" label="material name" [rbDebounceTime]="0" [rbInitialOpen]="true" <rb-form-input name="materialname" label="product name" [rbDebounceTime]="0" [rbInitialOpen]="true"
[rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" appValidate="stringOf" [rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" appValidate="stringOf"
(keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" ngModel (keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" ngModel
[appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true" [appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true"

View File

@ -1,5 +1,4 @@
<div class="header-addnew"> <div class="header-addnew">
<h2>Samples</h2>
<a routerLink="/samples/new" *ngIf="login.isLevel.write"> <a routerLink="/samples/new" *ngIf="login.isLevel.write">
<rb-icon-button icon="add" mode="primary" class="space-left">New sample</rb-icon-button> <rb-icon-button icon="add" mode="primary" class="space-left">New sample</rb-icon-button>
</a> </a>

View File

@ -2,6 +2,7 @@
.header-addnew { .header-addnew {
margin-bottom: 40px; margin-bottom: 40px;
height: 42px;
& > * { & > * {
display: inline; display: inline;

View File

@ -13,7 +13,6 @@ import {Router} from '@angular/router';
// TODO: turn off sort field // TODO: turn off sort field
// TODO reset sort when field is excluded // TODO reset sort when field is excluded
// TODO: material name to product
// TODO: Eh DPT // TODO: Eh DPT
// TODO: filter button // TODO: filter button
// TODO: check if connect-src to model works // TODO: check if connect-src to model works
@ -55,7 +54,7 @@ export class SamplesComponent implements OnInit {
sort: 'added-asc', sort: 'added-asc',
filters: [ filters: [
{field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']}, {field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']},
{field: 'material.name', label: 'Material name', active: false, autocomplete: [], mode: 'eq', values: ['']}, {field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: ['']},
{field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']}, {field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']},
{field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']}, {field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']},
{field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: ['']}, {field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: ['']},
@ -74,7 +73,7 @@ export class SamplesComponent implements OnInit {
keys: KeyInterface[] = [ keys: KeyInterface[] = [
{id: 'number', label: 'Number', active: true, sortable: true}, {id: 'number', label: 'Number', active: true, sortable: true},
{id: 'material.numbers', label: 'Material numbers', active: false, sortable: false}, {id: 'material.numbers', label: 'Material numbers', active: false, sortable: false},
{id: 'material.name', label: 'Material name', active: true, sortable: true}, {id: 'material.name', label: 'Product name', active: true, sortable: true},
{id: 'material.supplier', label: 'Supplier', active: false, sortable: true}, {id: 'material.supplier', label: 'Supplier', active: false, sortable: true},
{id: 'material.group', label: 'Material', active: true, sortable: true}, {id: 'material.group', label: 'Material', active: true, sortable: true},
{id: 'type', label: 'Type', active: true, sortable: true}, {id: 'type', label: 'Type', active: true, sortable: true},

View File

@ -1,4 +1,3 @@
<h2>Settings</h2>
<form #userForm="ngForm"> <form #userForm="ngForm">
<rb-form-input name="name" label="user name" appValidate="username" required [(ngModel)]="user.name" <rb-form-input name="name" label="user name" appValidate="username" required [(ngModel)]="user.name"

View File

@ -1,4 +1,3 @@
<h2>Templates</h2>
<rb-form-select name="collectionSelection" label="collection" <rb-form-select name="collectionSelection" label="collection"
[(ngModel)]="collection" (ngModelChange)="loadTemplates()"> [(ngModel)]="collection" (ngModelChange)="loadTemplates()">

View File

@ -1,4 +1,3 @@
<h2>Users</h2>
<rb-icon-button icon="add" mode="primary" (click)="addNewUser()">New user</rb-icon-button> <rb-icon-button icon="add" mode="primary" (click)="addNewUser()">New user</rb-icon-button>

View File

@ -41,6 +41,9 @@ export class UsersComponent implements OnInit {
} }
saveUser(user: UserModel) { saveUser(user: UserModel) {
console.log(user.models);
user.models = user.models.filter(e => e !== '');
console.log(user.models);
this.api.put<UserModel>('/user/' + user.origName, user.sendFormat('admin'), data => { this.api.put<UserModel>('/user/' + user.origName, user.sendFormat('admin'), data => {
user.deserialize(data); user.deserialize(data);
user.edit = false; user.edit = false;
@ -48,6 +51,7 @@ export class UsersComponent implements OnInit {
} }
saveNewUser() { saveNewUser() {
// this.newUser.models = this.newUser.models.filter(e => e !== '' || e !== undefined);
this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => { this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => {
this.newUser = null; this.newUser = null;
this.users.push(new UserModel().deserialize(data)); this.users.push(new UserModel().deserialize(data));

View File

@ -69,3 +69,7 @@ ul {
} }
} }
} }
.nav-main a.active {
font-weight: bold;
}