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

* commit '08d5ac8366279aae4da809a545c4949b2d583c6e':
  fixed changelog paging
This commit is contained in:
Veit Lukas (PEA4-Fe) 2020-08-28 16:59:05 +02:00
commit c3ab2810dc
26 changed files with 251 additions and 87 deletions

View File

@ -1,6 +1,9 @@
# DeFinMa - UI
This is the Angular front end for the digital fingerprint of plastics web page hosted in the bic
This is the Angular front end for the DeFinMa web page hosted on the BIC at
[https://definma.apps.de1.bosch-iot-cloud.com](https://definma.apps.de1.bosch-iot-cloud.com).
A deep insight into the project structure can be gained in the
[Bachelor Thesis](https://definma.apps.de1.bosch-iot-cloud.com/assets/docs/Veit-Lukas_Bachelor-Thesis.pdf).
## Bosch styled components
@ -8,7 +11,43 @@ Bosch styled Angular components were added following
[this](https://connect.bosch.com/blogs/f6aacf06-98dd-440c-b3a7-0c5a4ad4c1bd/entry/Getting_started_a_Bosch_styled_Angular_Project?lang=de_de)
guide, included using the `@inst-iot/bosch-angular-ui-components` package.
## Testing
## General structure
Unit and e2e tests are implemented and can be executed using `ng test` and `protractor ./e2e/protractor.conf.js`
respectively.
All components are displayed inside of the `app.component`, most of them directly as routes, see
[app-routing.module.ts](./src/app/app-routing.module.ts). For all routes except the `/documentation` the user has to be
logged in and has to have the right user level. Login is handled by the
[login.service](./src/app/services/login.service.ts), which also provides the `canActivate` function.
### Additional components
The [error.component](./src/app/error) provides an error pop over.
The [help.component](./src/app/help) provides the help information for all pages which can be accessed by the question
mark in the top right corner.
The [img-magnifier](./src/app/img-magnifier) is used for displaying an image with a magnifying glass when hovering over
the image.
[rb-custom-inputs](./src/app/rb-custom-inputs) provides Bosch styled components which were not included in the library.
These are an array input for displaying nested input elements for every array item, a button with icon and a table.
The [models](./src/app/models) folder provides TS classes for database objects.
### Services
The [api.service](./src/app/services/api.service.ts) provides API access with integrated authorization and error
handling.
The [autocomplete.service](./src/app/services/autocomplete.service.ts) uses the _QuickScore_ module for sorting
autocomplete results.
The [data.service](./src/app/services/data.service.ts) loads some resources needed in multiple Components. Take a look
at the `collectionMap` in line 19 for available resources.
The [login.service](./src/app/services/login.service.ts) provides login and logout as well as information about the
current user.
The [validation.service](./src/app/services/validation.service.ts) provides validation together with the
[validate.directive](./src/app/validate.directive.ts) for Angular Forms.

View File

@ -65,9 +65,7 @@
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
]
}
}
},

View File

@ -1,29 +0,0 @@
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}

View File

@ -9,9 +9,10 @@ import {TemplatesComponent} from './templates/templates.component';
import {SettingsComponent} from './settings/settings.component';
import {UsersComponent} from './users/users.component';
import {ChangelogComponent} from './changelog/changelog.component';
import {DocumentationDatabaseComponent} from './documentation-database/documentation-database.component';
import {DocumentationDatabaseComponent} from './documentation/documentation-database/documentation-database.component';
import {PredictionComponent} from './prediction/prediction.component';
import {ModelTemplatesComponent} from './model-templates/model-templates.component';
import {DocumentationArchitectureComponent} from './documentation/documentation-architecture/documentation-architecture.component';
const routes: Routes = [
@ -27,6 +28,7 @@ const routes: Routes = [
{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},
// if not authenticated

View File

@ -15,6 +15,7 @@
<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>
</nav>
</ng-container>

View File

@ -1,6 +1,6 @@
import {Component, isDevMode, OnInit} from '@angular/core';
import {LoginService} from './services/login.service';
import {NavigationStart, Router} from '@angular/router';
import {ActivatedRoute, NavigationStart, Router} from '@angular/router';
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
import {HelpComponent} from './help/help.component';
import {DataService} from './services/data.service';
@ -22,6 +22,7 @@ export class AppComponent implements OnInit{
constructor(
public login: LoginService,
public router: Router,
private route: ActivatedRoute,
public window: Window,
private modal: ModalService,
public d: DataService
@ -36,7 +37,7 @@ export class AppComponent implements OnInit{
ngOnInit() {
this.login.login().then(res => {
if (!res) {
if (!res && !(this.route.snapshot.url.length && this.route.snapshot.url[0].path === '/documentation')) {
this.router.navigate(['/']);
}
});

View File

@ -26,13 +26,12 @@ import { ParametersPipe } from './parameters.pipe';
import { SettingsComponent } from './settings/settings.component';
import { UsersComponent } from './users/users.component';
import { ChangelogComponent } from './changelog/changelog.component';
import { DocumentationDatabaseComponent } from './documentation-database/documentation-database.component';
import { DocumentationDatabaseComponent } from './documentation/documentation-database/documentation-database.component';
import { PredictionComponent } from './prediction/prediction.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
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';
@NgModule({
declarations: [
@ -56,7 +55,8 @@ import { SizePipe } from './size.pipe';
PredictionComponent,
HelpComponent,
ModelTemplatesComponent,
SizePipe
SizePipe,
DocumentationArchitectureComponent
],
imports: [
LocalStorageModule.forRoot({
@ -73,8 +73,7 @@ import { SizePipe } from './size.pipe';
ReactiveFormsModule,
FormFieldsModule,
CommonModule,
ChartsModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
ChartsModule
],
providers: [
ModalService,

View File

@ -26,7 +26,10 @@ export class ChangelogComponent implements OnInit {
loadChangelog(page = 0) {
this.api.get<ChangelogModel[]>(`/changelog/${
new Date(new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000).toISOString()
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) {

View File

@ -0,0 +1,50 @@
<h4>Architecture</h4>
<img src="/assets/imgs/architecture.svg" alt="architecture" class="space-below">
<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/api-doc/">
https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/
</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>
</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.
</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>.
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentationArchitectureComponent } from './documentation-architecture.component';
describe('DocumentationArchitectureComponent', () => {
let component: DocumentationArchitectureComponent;
let fixture: ComponentFixture<DocumentationArchitectureComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DocumentationArchitectureComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DocumentationArchitectureComponent);
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-architecture',
templateUrl: './documentation-architecture.component.html',
styleUrls: ['./documentation-architecture.component.scss']
})
export class DocumentationArchitectureComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,7 +1,45 @@
<h2>Database</h2>
<p class="space-below">
Structure of the MongoDB instance running on the BIC, storing all application data.
<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/">
https://definma-db.apps.de1.bosch-iot-cloud.com/api-doc/
</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).<br>
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>TLDR:
</p>
For the first time:
<ul>
<li>cf login</li>
<li>cf enable-ssh &lt;app-name, probably definma-api&gt;</li>
<li>cf create-service-key &lt;service-name, probably definmadb&gt; &lt;key-name, can be whatever&gt;</li>
</ul>
Every time:
<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>
</ul>
<h6>Backup</h6>
<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/community">MongoDB server</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,
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;
/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
<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
<a href="https://inside-docupedia.bosch.com/confluence/pages/viewpage.action?pageId=565402281">MongoDB FAQ</a>
</p>
<h4>Database model</h4>
@ -274,8 +312,34 @@
<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>model_files</th><th></th><th></th></tr>
<tr>
<td>_id</td>
<td>Automatically generated unique id</td>

View File

@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentationDatabaseComponent } from './documentation-database.component';
import {Component, Input} from '@angular/core';
import {RbCustomInputsModule} from '../rb-custom-inputs/rb-custom-inputs.module';
import {RbCustomInputsModule} from '../../rb-custom-inputs/rb-custom-inputs.module';
// TODO

View File

@ -1,30 +1,11 @@
<h2>Documentation</h2>
<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>
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>
</p>
<h4>Introduction</h4>
<p>
<a [href]="api.hostName + '/static/intro-presentation/index.html'">
View the presentation explaining the main functions
</a>
</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>
@ -96,13 +77,6 @@
</tr>
</rb-table>
<h4>Architecture</h4>
<img src="/assets/imgs/architecture.svg" alt="architecture" class="space-below">
<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.
</p>

View File

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

View File

@ -1,6 +1,8 @@
import {BaseModel} from './base.model';
import {IdModel} from './id.model';
export class ChangelogModel extends BaseModel {
_id: IdModel = null;
date: Date;
action: string;
collection: string;

View File

@ -13,13 +13,14 @@
[rbFormInputAutocomplete]="autocomplete.bind(this, materialNames)" appValidate="stringOf"
(keydown)="preventDefault($event)" (ngModelChange)="findMaterial($event)" ngModel
[appValidateArgs]="[materialNames]" required [(ngModel)]="material.name" [autofocus]="true"
*ngIf="baseSample.material.name !== undefined || mode === 'new'"
*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="baseSample.material.name !== undefined">
(click)="setNewMaterial(!newMaterial)"
*ngIf="mode === 'new' || (baseSample.material && baseSample.material.name !== undefined)">
New material
</rb-icon-button>
</div>

View File

@ -118,6 +118,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
) { }
ngOnInit(): void {
console.log(this.baseSample);
this.mode = this.router.url === '/samples/new' ? 'new' : '';
this.loading = 7;
this.d.load('materials', () => {
@ -378,8 +379,6 @@ export class SampleComponent implements OnInit, AfterContentChecked {
cmSave() { // save measurements and conditions
this.samples.forEach(sample => {
if (sample.condition.condition_template) { // condition was set
console.log(sample.condition);
console.log(this.d.id.conditionTemplates[sample.condition.condition_template]);
this.api.put('/sample/' + sample._id,
{condition: pick(sample.condition,
['condition_template', ...this.d.id.conditionTemplates[sample.condition.condition_template].parameters.map(e => e.name)]

View File

@ -45,7 +45,6 @@ export class ApiService {
observable.subscribe(data => {
f(data.body, undefined, data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {}));
}, err => {
console.log(f.length);
if (f.length > 1) {
f(undefined, err, undefined);
}

Binary file not shown.

Binary file not shown.

View File

@ -6,8 +6,6 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#1976d2">
</head>
<body>
<app-root></app-root>

View File

@ -46,3 +46,26 @@ button::-moz-focus-inner {
.supergraphic {
background-image: url("/assets/imgs/supergraphic.svg");
}
//list styles
ul {
margin: 10px 0 10px 25px;
padding-left: 10px;
list-style: none;
li {
&:before {
float: left;
content: '';
height: 8px;
width: 8px;
background: rgb(0,0,0);
position: relative;
top: 0.5em;
margin-left: -1.5em;
margin-right: .3em;
text-align: right;
}
}
}