Index: DRT application install & config - v.0.2.3.txt =================================================================== diff -u --- DRT application install & config - v.0.2.3.txt (revision 0) +++ DRT application install & config - v.0.2.3.txt (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,39 @@ + +A. Install dependencies: + + 1. Install node v16 + + sudo apt update && sudo apt install curl + + curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - + sudo apt-get install -y nodejs + + node -v (confirm version 16 is running) + + + 2. Install Redis + + sudo apt install redis-server + + +B. Deploy latest version of the DRT app (git clone preferred) + +C. Install app dependencies + + npm install + +D. Change configuration for DRT environment + + change apps/mt-client/src/environments/environment.prod.ts - with specific env conf + + change .env - with specific env conf + +E. Build application + + npm run build:client + + npm run build:api + +F. Start application + + node ./dist/apps/mt-api/main.js \ No newline at end of file Index: angular.json =================================================================== diff -u --- angular.json (revision 0) +++ angular.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,225 @@ +{ + "version": 1, + "cli": { + "defaultCollection": "@nrwl/angular" + }, + "defaultProject": "mt-client", + "schematics": { + "@nrwl/angular": { + "application": { + "linter": "eslint" + }, + "library": { + "linter": "eslint" + }, + "storybook-configuration": { + "linter": "eslint" + } + }, + "@nrwl/angular:application": { + "style": "scss", + "linter": "eslint", + "unitTestRunner": "jest", + "e2eTestRunner": "cypress" + }, + "@nrwl/angular:library": { + "style": "scss", + "linter": "eslint", + "unitTestRunner": "jest" + }, + "@nrwl/angular:component": { + "style": "scss" + } + }, + "projects": { + "mt-client": { + "projectType": "application", + "root": "apps/mt-client", + "sourceRoot": "apps/mt-client/src", + "prefix": "dia-man-tools", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/apps/mt-client", + "index": "apps/mt-client/src/index.html", + "main": "apps/mt-client/src/main.ts", + "polyfills": "apps/mt-client/src/polyfills.ts", + "tsConfig": "apps/mt-client/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "apps/mt-client/src/favicon.ico", + "apps/mt-client/src/assets" + ], + "styles": ["apps/mt-client/src/styles.scss"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "apps/mt-client/src/environments/environment.ts", + "with": "apps/mt-client/src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "integration": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "apps/mt-client/src/environments/environment.ts", + "with": "apps/mt-client/src/environments/environment.int.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "mt-client:build:production" + }, + "development": { + "browserTarget": "mt-client:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "mt-client:build" + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "apps/mt-client/src/**/*.ts", + "apps/mt-client/src/**/*.html" + ] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "outputs": ["coverage/apps/mt-client"], + "options": { + "jestConfig": "apps/mt-client/jest.config.js", + "passWithNoTests": true + } + } + } + }, + "mt-client-e2e": { + "root": "apps/mt-client-e2e", + "sourceRoot": "apps/mt-client-e2e/src", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/mt-client-e2e/cypress.json", + "tsConfig": "apps/mt-client-e2e/tsconfig.e2e.json", + "devServerTarget": "mt-client:serve:development" + }, + "configurations": { + "production": { + "devServerTarget": "mt-client:serve:production" + } + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": ["apps/mt-client-e2e/**/*.{js,ts}"] + } + } + } + }, + "mt-api": { + "root": "apps/mt-api", + "sourceRoot": "apps/mt-api/src", + "projectType": "application", + "architect": { + "build": { + "builder": "@nrwl/node:build", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/mt-api", + "main": "apps/mt-api/src/main.ts", + "tsConfig": "apps/mt-api/tsconfig.app.json", + "assets": ["apps/mt-api/src/assets"] + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "fileReplacements": [ + { + "replace": "apps/mt-api/src/environments/environment.ts", + "with": "apps/mt-api/src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "builder": "@nrwl/node:execute", + "options": { + "buildTarget": "mt-api:build" + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": ["apps/mt-api/**/*.ts"] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "outputs": ["coverage/apps/mt-api"], + "options": { + "jestConfig": "apps/mt-api/jest.config.js", + "passWithNoTests": true + } + } + } + } + } +} Index: api-test.http =================================================================== diff -u --- api-test.http (revision 0) +++ api-test.http (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,74 @@ +### +POST https://mft.diality.integration.kebormed.com/register +#POST http://localhost:3000/register +Content-Type: application/json +DEVICE_SN:00:00:00:00:07 +DEVICE_IP:cs.diality.integration.kebormed.com + +{ + "datasourceId": 1, + "name": "Device MFT Created3", + "hdSerialNumber": "00:00:00:00:07", + "dgSerialNumber": "00:00:00:11", + "softwareVersion": "1.2" +} + +### + +POST https://mft.diality.integration.kebormed.com/api/validation +Content-Type: application/json +DEVICE_SN:00:00:00:00:204 +DEVICE_IP:cs.diality.integration.kebormed.com + + + +### + +POST http://localhost:3000/register +Content-Type: application/json +DEVICE_SN:01:00:00:00:07 +DEVICE_IP:cs.diality.integration.kebormed.com + +{ + "datasourceId": 1, + "name": "Device Cip", + "hdSerialNumber": "01:00:00:00:01", + "dgSerialNumber": "1212a1234", + "softwareVersion": "1.2" +} + +### + +DELETE http://localhost:3000/client/delete/device/18:00:00:00:123 +Accept: application/json + +### + +GET http://localhost:3000/client/devices +Accept: application/json + +### + + +POST http://localhost:3000/api/validation +Content-Type: application/json +DEVICE_SN:18:00:00:00:117 +DEVICE_IP:cs.diality.integration.kebormed.com + + + +### + + +HEAD https://cs.diality.integration.kebormed.com/local/register +Accept: application/json + +### + +PUT https://cs.diality.integration.kebormed.com/manufacturing_tool +Content-Type: application/json +Accept: application/json + +{ + "url": "https://mft.diality.integration.kebormed.com" +} Index: apps/.gitkeep =================================================================== diff -u --- apps/.gitkeep (revision 0) +++ apps/.gitkeep (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ \ No newline at end of file Index: apps/mt-api/.eslintrc.json =================================================================== diff -u --- apps/mt-api/.eslintrc.json (revision 0) +++ apps/mt-api/.eslintrc.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,21 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["apps/mt-api/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} Index: apps/mt-api/jest.config.js =================================================================== diff -u --- apps/mt-api/jest.config.js (revision 0) +++ apps/mt-api/jest.config.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,14 @@ +module.exports = { + displayName: 'mt-api', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]s$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/mt-api', +}; Index: apps/mt-api/src/app/.gitkeep =================================================================== diff -u --- apps/mt-api/src/app/.gitkeep (revision 0) +++ apps/mt-api/src/app/.gitkeep (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ \ No newline at end of file Index: apps/mt-api/src/app/app.module.ts =================================================================== diff -u --- apps/mt-api/src/app/app.module.ts (revision 0) +++ apps/mt-api/src/app/app.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,39 @@ +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bull'; +import { DbModule } from './db/db.module'; +import { DeviceModule } from './device/device.module'; +import { CloudModule } from './cloud/cloud.module'; +import { ManagementModule } from './management/management.module'; +import { ClientModule } from './client/client.module'; +import { MainController } from './main.controller'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { HeartBeatCron } from './heart-beat.cron'; +import { SocketModule } from './socket/socket.module'; + + +@Module({ + imports: [ + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (config: ConfigService) => { + return { + redis: { + host: config.get('REDIS_HOST'), + port: config.get('REDIS_PORT') + } + }; + }, + inject: [ConfigService] + }), + DbModule, + DeviceModule, + CloudModule, + ManagementModule, + ClientModule, + SocketModule + ], + controllers: [MainController], + providers: [HeartBeatCron] +}) +export class AppModule { +} Index: apps/mt-api/src/app/auth/auth.module.ts =================================================================== diff -u --- apps/mt-api/src/app/auth/auth.module.ts (revision 0) +++ apps/mt-api/src/app/auth/auth.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ +import {HttpModule, Module} from '@nestjs/common'; +import {AuthService} from './auth.service'; +import {SocketModule} from '../socket/socket.module'; +import {ConfigModule} from "@nestjs/config"; + +@Module({ + imports: [ + SocketModule, + ConfigModule, + HttpModule.register({}), + ], + providers: [AuthService], + exports: [AuthService], +}) +export class AuthModule { +} Index: apps/mt-api/src/app/auth/auth.service.ts =================================================================== diff -u --- apps/mt-api/src/app/auth/auth.service.ts (revision 0) +++ apps/mt-api/src/app/auth/auth.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,83 @@ +import {HttpService, Injectable, Logger} from '@nestjs/common'; +import {Client, Issuer, TokenSet} from "openid-client"; +import {ConfigService} from "@nestjs/config"; + +@Injectable() +export class AuthService { + private Client!: Client; + private _tokenSet!: TokenSet; + private logger: Logger = new Logger('AuthService'); + + constructor( + private readonly http: HttpService, + private readonly config: ConfigService + ) { + } + + async client() { + if (!this.Client) { + const issuer = await Issuer.discover(this.config.get('KC_ISSUER')); + const {Client} = issuer; + + this.Client = new Client({ + client_id: this.config.get('KC_CLIENT'), + client_secret: this.config.get('KC_SECRET'), + }); + this.Client.authorizationUrl({scope: 'offline_access'}) + } + return this.Client; + } + + get accessToken() { + return this._tokenSet.access_token; + } + + get refreshToken() { + return this._tokenSet.refresh_token; + } + + async login() { + const Client = await this.client(); + return Client.grant({ + grant_type: 'client_credentials', + }).then((c) => { + this.logger.debug('Get access token'); + this._tokenSet = c; + return c; + }).catch(error => { + this.logger.error(`Error login ${error}`); + }); + } + + async refresh() { + const Client = await this.client(); + return Client.refresh(this._tokenSet.refresh_token).then((c) => { + this.logger.debug('Refresh token'); + this._tokenSet = c; + return c; + }).catch(error => { + this.logger.error(`Error refreshing token ${error}`); + return this.login(); + }); + } + + get isTokenExpired(): boolean { + return this._tokenSet.expired(); + } + + async isActive() { + const Client = await this.client(); + return Client.introspect(this.accessToken).then(({active}) => active); + } + + async tokenLife() { + const expiresIn = this._tokenSet.expires_in / 60; + const lifeTime = Number(this.config.get('TOKEN_LIFETIME')); + return Number((100 - (lifeTime - expiresIn) * 100 / lifeTime).toFixed(1)); + } + + async logout() { + const Client = await this.client(); + return await Client.revoke(this._tokenSet.access_token); + } +} Index: apps/mt-api/src/app/client/client.controller.ts =================================================================== diff -u --- apps/mt-api/src/app/client/client.controller.ts (revision 0) +++ apps/mt-api/src/app/client/client.controller.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,63 @@ +import {Controller, Delete, Get, HttpException, Param, Res} from '@nestjs/common'; +import {DbService} from '../db/db.service'; +import * as crypto from 'crypto'; +import {CloudService} from '../cloud/cloud.service'; +import {Response} from 'express'; + +@Controller('client') +export class ClientController { + constructor( + private readonly db: DbService, + private readonly cloudService: CloudService + ) { + } + + @Get('devices') + async devices() { + const devices = await this.db.get(); + return devices.map(device => JSON.parse(device)); + } + + @Get('devices/completed') + completed() { + return this.db.completed(); + } + + @Get('logs/:sn') + async log(@Param('sn') sn: string) { + const hash = crypto.createHash('md5').update(sn).digest('hex'); + const logs = await this.db.logs(hash); + return Object.entries(logs) + .map(([key, value]) => ({type: key, ...JSON.parse(String(value))})) + .sort((a: any, b: any) => a.time - b.time); + } + + /** + * Delete log for a specific device + * @param sn + */ + @Delete('delete/:sn') + delete(@Param('sn') sn: string) { + const hash = crypto.createHash('md5').update(sn).digest('hex'); + return this.db.delete(hash); + } + + @Delete('delete/device/:sn') + async deleteDevice( + @Param('sn') sn: string, + @Res() res: Response + ) { + const hash = crypto.createHash('md5').update(sn).digest('hex'); + try { + const deviceId = await this.db.getDevice(hash); + if (deviceId) { + await this.cloudService.deleteDevice(Number(deviceId)); + } + await this.delete(sn); + res.status(204).send(); + return {sn}; + } catch (error) { + throw new HttpException(error.data, error.status); + } + } +} Index: apps/mt-api/src/app/client/client.module.ts =================================================================== diff -u --- apps/mt-api/src/app/client/client.module.ts (revision 0) +++ apps/mt-api/src/app/client/client.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { ClientController } from './client.controller'; +import { DbModule } from '../db/db.module'; +import { CloudModule } from '../cloud/cloud.module'; + +@Module({ + imports: [ + DbModule, + CloudModule + ], + controllers: [ClientController] +}) +export class ClientModule { +} Index: apps/mt-api/src/app/cloud/auth.cron.ts =================================================================== diff -u --- apps/mt-api/src/app/cloud/auth.cron.ts (revision 0) +++ apps/mt-api/src/app/cloud/auth.cron.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,48 @@ +import {HttpService, Injectable, Logger} from '@nestjs/common'; +import {Cron, CronExpression} from '@nestjs/schedule'; +import {AuthService} from '../auth/auth.service'; +import {ConfigService} from '@nestjs/config'; +import {SocketService} from '../socket/socket.service'; + +@Injectable() +export class AuthCron { + private logger: Logger = new Logger('AuthCron'); + + constructor( + private readonly http: HttpService, + private readonly authService: AuthService, + private readonly socketService: SocketService, + private readonly config: ConfigService + ) { + } + + + @Cron(CronExpression.EVERY_MINUTE) + async handleCron() { + const tokenLife = await this.authService.tokenLife(); + this.logger.debug(`Check token life ${String(tokenLife)}%`); + if (tokenLife < 50) { + const isActive = await this.authService.isActive(); + this.logger.debug(`Is Active Token ${isActive}`); + if (isActive) { + await this.authService.refresh().then(() => { + this.http.axiosRef.defaults = { + baseURL: this.config.get('CLOUD_ENDPOINT'), + headers: { + Authorization: `Bearer ${this.authService.accessToken}` + } + }; + }); + } else { + await this.authService.login().then(() => { + this.http.axiosRef.defaults = { + baseURL: this.config.get('CLOUD_ENDPOINT'), + headers: { + Authorization: `Bearer ${this.authService.accessToken}` + } + }; + }); + } + } + } +} Index: apps/mt-api/src/app/cloud/cloud.module.ts =================================================================== diff -u --- apps/mt-api/src/app/cloud/cloud.module.ts (revision 0) +++ apps/mt-api/src/app/cloud/cloud.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,42 @@ +import {AuthService} from '../auth/auth.service'; +import {AuthModule} from '../auth/auth.module'; +import {AuthCron} from './auth.cron'; +import {CloudService} from './cloud.service'; +import {DbModule} from '../db/db.module'; +import {SocketModule} from '../socket/socket.module'; +import {HttpModule, Module} from '@nestjs/common'; +import {ConfigModule, ConfigService} from '@nestjs/config'; +import {ScheduleModule} from '@nestjs/schedule'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + SocketModule, + AuthModule, + DbModule, + HttpModule.registerAsync({ + imports: [ConfigModule, AuthModule], + useFactory: (config: ConfigService, authService: AuthService) => { + return authService.login().then(() => { + return { + baseURL: config.get('CLOUD_ENDPOINT'), + headers: { + Authorization: `Bearer ${authService.accessToken}` + } + }; + }); + }, + inject: [ConfigService, AuthService] + }), + ScheduleModule.forRoot() + ], + providers: [ + CloudService, + AuthCron + ], + exports: [ + CloudService + ] +}) +export class CloudModule { +} Index: apps/mt-api/src/app/cloud/cloud.service.ts =================================================================== diff -u --- apps/mt-api/src/app/cloud/cloud.service.ts (revision 0) +++ apps/mt-api/src/app/cloud/cloud.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,46 @@ +import { HttpService, Injectable } from '@nestjs/common'; + +@Injectable() +export class CloudService { + constructor(private readonly http: HttpService) { + } + + async register(data) { + try { + const response = await this.http.post('/api/admin/device', data).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + } + } + + async deleteDevice(id: number) { + try { + const response = await this.http.delete(`/api/admin/device/${id}`).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + // throw { data: response.data, status: response.status, path: response.config.url }; + } + } + + async updateDeviceStatus(id: number) { + try { + const response = await this.http.put(`/api/admin/device/${id}/state/2`, null).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + } + + } + + async getCert(id: number) { + try { + const response = await this.http.get(`/api/admin/device/${id}/certificate/generate`).toPromise(); + return response.data; + } catch ({ response }) { + + throw { data: response.data, status: response.status, path: response.config.url }; + } + } +} Index: apps/mt-api/src/app/const.ts =================================================================== diff -u --- apps/mt-api/src/app/const.ts (revision 0) +++ apps/mt-api/src/app/const.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,24 @@ +import * as crypto from 'crypto'; + +export function hash(value: string) { + return crypto.createHash('md5').update(value).digest('hex'); +} + +export enum EVENTS { + TRY_REGISTER = 'TRY_REGISTER', + REGISTER_ERROR = 'REGISTER_ERROR', + REGISTER_COMPLETE = 'REGISTER_COMPLETE', + TRY_GET_CERTIFICATE = 'TRY_GET_CERTIFICATE', + TRY_SET_DEVICE_STATUS = 'TRY_SET_DEVICE_STATUS', + CERTIFICATE_ERROR = 'CERTIFICATE_ERROR', + SET_CERTIFICATE = 'SET_CERTIFICATE', + CONNECTIVITY_TEST = 'CONNECTIVITY_TEST', + CONNECTIVITY_TEST_ERROR = 'CONNECTIVITY_TEST_ERROR', + SET_CERTIFICATE_ERROR = 'SET_CERTIFICATE_ERROR', + INITIATE_FACTORY_RESET = 'INITIATE_FACTORY_RESET', + FACTORY_RESET_ERROR = 'FACTORY_RESET_ERROR', + SET_DEVICE_STATUS_FAILED = 'SET_DEVICE_STATUS_FAILED', + DEVICE_VALIDATION_ERROR = 'DEVICE_VALIDATION_ERROR', + CONNECTED_CLIENTS = 'CONNECTED_CLIENTS', + COMPLETED = 'COMPLETED' +} Index: apps/mt-api/src/app/db/db.module.ts =================================================================== diff -u --- apps/mt-api/src/app/db/db.module.ts (revision 0) +++ apps/mt-api/src/app/db/db.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,23 @@ +import {Module} from '@nestjs/common'; +import {DbService} from './db.service'; +import {RedisModule} from 'nestjs-redis'; +import {ConfigModule, ConfigService} from '@nestjs/config'; + +@Module({ + imports: [ + RedisModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (config: ConfigService): any => { + return { + host: config.get('REDIS_HOST') as string, + port: config.get('REDIS_PORT') as string + }; + }, + inject: [ConfigService] + }) + ], + providers: [DbService], + exports: [DbService] +}) +export class DbModule { +} Index: apps/mt-api/src/app/db/db.service.ts =================================================================== diff -u --- apps/mt-api/src/app/db/db.service.ts (revision 0) +++ apps/mt-api/src/app/db/db.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,75 @@ +import {Injectable} from '@nestjs/common'; +import {RedisService} from 'nestjs-redis'; +import {LogData} from '../models/data.model'; + +@Injectable() +export class DbService { + constructor(private readonly redisService: RedisService) { + } + + private get redis() { + return this.redisService.getClient(); + } + + setDevice(id: string, deviceId: string) { + return this.redis.set(`devices:${id}`, deviceId); + } + + getDevice(id: string) { + return this.redis.get(`devices:${id}`); + } + + deleteDevice(id: string) { + return this.redis.del(`devices:${id}`); + } + + create(key: string, value: any) { + return this.redis.set(key, JSON.stringify(value)); + } + + add(id, key, log: LogData) { + return this.redis.multi().set(id, JSON.stringify({ + ...log.metadata, + time: log.time + })).zadd('errors', String(0), id).hset(`${id}:errors`, key, JSON.stringify({ + ...log.error, + time: log.time + })).exec(); + } + + async complete(id: string, payload?: any) { + const device = await this.redis.get(id).then(data => JSON.parse(data)); + const logs = await this.logs(id).then(l => Object.entries(l).map(([key, value]) => ({type: key, ...JSON.parse(String(value))}))); + const deviceId = await this.getDevice(id); + return this.delete(id).then(() => { + return this.redis.zadd(`completed`, 0, JSON.stringify({...device, logs, deviceId})); + }); + } + + async get() { + const ids: string[] = await this.redis.zrange('errors', 0, -1); + if (ids.length) { + return this.redis.mget(...ids); + } + return []; + } + + async completed() { + const data = await this.redis.zrange('completed', 0, -1); + return data.map(record => JSON.parse(record)); + } + + async logs(id: string) { + return this.redis.hgetall(`${id}:errors`); + } + + async delete(id: string) { + const errors = await this.redis.hkeys(`${id}:errors`); + await this.redis.multi({pipeline: false}); + await this.redis.del(id); + await this.redis.zrem('errors', id); + await this.deleteDevice(id); + errors.forEach((key) => this.redis.hdel(`${id}:errors`, key)); + await this.redis.exec(); + } +} Index: apps/mt-api/src/app/device/device.module.ts =================================================================== diff -u --- apps/mt-api/src/app/device/device.module.ts (revision 0) +++ apps/mt-api/src/app/device/device.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,17 @@ +import { HttpModule, Module } from '@nestjs/common'; +import { DeviceService } from './device.service'; +import { ConfigModule } from '@nestjs/config'; +import * as https from 'https'; + +@Module({ + imports: [ + ConfigModule, + HttpModule.register({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }) + }) + ], + providers: [DeviceService], + exports: [DeviceService] +}) +export class DeviceModule { +} Index: apps/mt-api/src/app/device/device.service.ts =================================================================== diff -u --- apps/mt-api/src/app/device/device.service.ts (revision 0) +++ apps/mt-api/src/app/device/device.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,50 @@ +import { HttpService, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class DeviceService { + constructor( + private readonly cfg: ConfigService, + private readonly http: HttpService + ) { + } + + private get schema(): string { + return this.cfg.get('DEVICE_HTTP_SCHEMA'); + } + + private get port(): string { + return this.cfg.get('DEVICE_HTTP_PORT'); + } + + private endpoint(ip: string): string { + return `${this.schema}://${ip}:${this.port}`; + } + + async setCert(ip: string, payload: any) { + try { + const response = await this.http.put(`${this.endpoint(ip)}/credentials`, payload).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + } + } + + async connectivityTest(ip: string) { + try { + const response = await this.http.head(`${this.endpoint(ip)}/validate`).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + } + } + + async reset(ip: string) { + try { + const response = await this.http.head(`${this.endpoint(ip)}/reset`).toPromise(); + return response.data; + } catch ({ response }) { + throw { data: response.data, status: response.status, path: response.config.url }; + } + } +} Index: apps/mt-api/src/app/heart-beat.cron.ts =================================================================== diff -u --- apps/mt-api/src/app/heart-beat.cron.ts (revision 0) +++ apps/mt-api/src/app/heart-beat.cron.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { SocketService } from './socket/socket.service'; +import { Logger } from '@nestjs/common'; +import { EVENTS } from './const'; + +@Injectable() +export class HeartBeatCron { + private logger: Logger = new Logger('HeartBeatCron'); + + constructor(private readonly socketService: SocketService) { + } + + @Cron(CronExpression.EVERY_5_SECONDS) + handleCron() { + this.socketService.send({ event: EVENTS.CONNECTED_CLIENTS, payload: this.socketService.clients }); + } +} Index: apps/mt-api/src/app/main.controller.ts =================================================================== diff -u --- apps/mt-api/src/app/main.controller.ts (revision 0) +++ apps/mt-api/src/app/main.controller.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,30 @@ +import {Body, Controller, Get, Headers, Logger, Post} from '@nestjs/common'; +import {QService} from './management/q.service'; + +@Controller() +export class MainController { + private readonly logger = new Logger(MainController.name); + + constructor( + private readonly qService: QService + ) { + } + + @Get() + info() { + return { + v: '0.0.1' + }; + } + + @Post('register') + async register(@Body() deviceDto: any, @Headers('device_sn') sn, @Headers('device_ip') ip) { + return this.qService.register({...deviceDto, hdSerialNumber: sn}, {sn, ip}); + } + + @Post('validation') + async validationResult(@Body() data: never, @Headers('device_sn') sn, @Headers('device_ip') ip) { + this.logger.log('VALIDATION CONTROLLER'); + return this.qService.validation(data, {sn, ip}); + } +} Index: apps/mt-api/src/app/management/certificate.processor.ts =================================================================== diff -u --- apps/mt-api/src/app/management/certificate.processor.ts (revision 0) +++ apps/mt-api/src/app/management/certificate.processor.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,99 @@ +import { OnQueueCompleted, OnQueueFailed, Process, Processor } from '@nestjs/bull'; +import { Job } from 'bull'; +import { JobData } from '../models/data.model'; +import { CloudService } from '../cloud/cloud.service'; +import { DeviceService } from '../device/device.service'; +import { DbService } from '../db/db.service'; +import { SocketService } from '../socket/socket.service'; +import { QService } from './q.service'; +import { EVENTS, hash } from '../const'; + +@Processor('device-request') +export class CertificateProcessor { + constructor( + private readonly qService: QService, + private readonly cloudService: CloudService, + private readonly deviceService: DeviceService, + private readonly db: DbService, + private readonly socketService: SocketService + ) { + } + + @Process('certificate') + async certificate(job: Job) { + const errorResponse = { + error: { + log: job.data.metadata + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.TRY_GET_CERTIFICATE, errorResponse); + return this.cloudService.getCert(job.data.payload.id); + } + + @OnQueueFailed() + async fail(job: Job, error: any) { + const errorResponse = { + error: { + log: error + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.CERTIFICATE_ERROR, errorResponse); + this.socketService.send({ event: EVENTS.CERTIFICATE_ERROR }); + console.log('CERT JOB FAILS', error); + } + + @OnQueueCompleted() + async completed(job: Job, result) { + try { + await this.deviceService.setCert(job.data.metadata.ip, { + certificate: result.certificate, + public_key: result.publicKey, + private_key: result.privateKey + }).then(async (res) => { + await this.db.add(hash(job.data.metadata.sn), EVENTS.SET_CERTIFICATE, { + metadata: job.data.metadata, + time: Date.now(), + error: { + log: res + } + }); + await this.deviceService.connectivityTest(job.data.metadata.ip) + .then(async (res) => { + + await this.db.add(hash(job.data.metadata.sn), EVENTS.CONNECTIVITY_TEST, { + metadata: job.data.metadata, + time: Date.now(), + error: { log: res } + }); + this.socketService.send({ event: EVENTS.CONNECTIVITY_TEST }); + + return res; + }).catch(async (error) => { + await this.db.add(hash(job.data.metadata.sn), EVENTS.CONNECTIVITY_TEST_ERROR, { + metadata: job.data.metadata, + time: Date.now(), + error: { error } + }); + + this.socketService.send({ event: EVENTS.CONNECTIVITY_TEST_ERROR }); + + }); + return res; + }); + } catch (error) { + const errorResponse = { + error: { + log: error + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.SET_CERTIFICATE_ERROR, errorResponse); + this.socketService.send({ event: EVENTS.SET_CERTIFICATE_ERROR }); + } + } +} Index: apps/mt-api/src/app/management/management.module.ts =================================================================== diff -u --- apps/mt-api/src/app/management/management.module.ts (revision 0) +++ apps/mt-api/src/app/management/management.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,38 @@ +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bull'; +import { CertificateProcessor } from './certificate.processor'; +import { QService } from './q.service'; +import { CloudModule } from '../cloud/cloud.module'; +import { DeviceModule } from '../device/device.module'; +import { DbModule } from '../db/db.module'; +import { SocketModule } from '../socket/socket.module'; +import { RegisterProcessor } from './register.processor'; +import { ValidationProcessor } from './validation.processor'; + +@Module({ + imports: [ + SocketModule, + CloudModule, + DeviceModule, + DbModule, + BullModule.registerQueue( + { name: 'cloud-request' }, + { name: 'device-request' }, + { name: 'validation-processor' } + )], + providers: [ + RegisterProcessor, + CertificateProcessor, + ValidationProcessor, + QService + ], + exports: [ + RegisterProcessor, + CertificateProcessor, + ValidationProcessor, + QService + ] +}) +export class ManagementModule { + +} Index: apps/mt-api/src/app/management/q.service.ts =================================================================== diff -u --- apps/mt-api/src/app/management/q.service.ts (revision 0) +++ apps/mt-api/src/app/management/q.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; + +@Injectable() +export class QService { + constructor( + @InjectQueue('cloud-request') private cloudQueue: Queue, + @InjectQueue('device-request') private deviceQueue: Queue, + @InjectQueue('validation-processor') private validationQueue: Queue + ) { + } + + certificate(payload, metadata) { + return this.deviceQueue.add('certificate', { payload, metadata }); + } + + register(payload, metadata) { + return this.cloudQueue.add('register', { payload, metadata }); + } + + validation(payload, metadata) { + return this.validationQueue.add('validation', { payload, metadata }); + } +} Index: apps/mt-api/src/app/management/register.processor.ts =================================================================== diff -u --- apps/mt-api/src/app/management/register.processor.ts (revision 0) +++ apps/mt-api/src/app/management/register.processor.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,71 @@ +import { OnQueueCompleted, OnQueueFailed, Process, Processor } from '@nestjs/bull'; +import { Job } from 'bull'; +import { CloudService } from '../cloud/cloud.service'; +import { DbService } from '../db/db.service'; +import { JobData } from '../models/data.model'; +import { DeviceService } from '../device/device.service'; +import { QService } from './q.service'; +import { SocketService } from '../socket/socket.service'; +import { EVENTS, hash } from '../const'; + +@Processor('cloud-request') +export class RegisterProcessor { + + constructor( + private readonly qService: QService, + private readonly cloudService: CloudService, + private readonly deviceService: DeviceService, + private readonly db: DbService, + private readonly socketService: SocketService + ) { + } + + + @Process('register') + async register(job: Job) { + const errorResponse = { + error: { + log: { + metadata: job.data.metadata, + payload: job.data.payload + } + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.TRY_REGISTER, errorResponse); + this.socketService.send({ event: EVENTS.TRY_REGISTER }); + return this.cloudService.register(job.data.payload); + } + + @OnQueueFailed() + async fail(job: Job, error: any) { + const errorResponse = { + error: { + log: error + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.REGISTER_ERROR, errorResponse); + this.socketService.send({ event: EVENTS.REGISTER_ERROR }); + console.log('JOB REGISTER FAILS', error); + } + + @OnQueueCompleted() + async completed(job: Job, result) { + const errorResponse = { + error: { + log: result + }, + metadata: job.data.metadata, + time: Date.now() + }; + const { id } = result; + await this.db.setDevice(hash(job.data.metadata.sn), id); + await this.db.add(hash(job.data.metadata.sn), EVENTS.REGISTER_COMPLETE, errorResponse); + await this.qService.certificate(result, job.data.metadata); + this.socketService.send({ event: EVENTS.REGISTER_COMPLETE }); + console.log('JOB REGISTER COMPLETE', result); + } +} Index: apps/mt-api/src/app/management/validation.processor.ts =================================================================== diff -u --- apps/mt-api/src/app/management/validation.processor.ts (revision 0) +++ apps/mt-api/src/app/management/validation.processor.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,98 @@ +import { OnQueueCompleted, OnQueueFailed, Process, Processor } from '@nestjs/bull'; +import { QService } from './q.service'; +import { CloudService } from '../cloud/cloud.service'; +import { DeviceService } from '../device/device.service'; +import { DbService } from '../db/db.service'; +import { SocketService } from '../socket/socket.service'; +import { Job } from 'bull'; +import { JobData } from '../models/data.model'; +import { EVENTS, hash } from '../const'; + +@Processor('validation-processor') +export class ValidationProcessor { + + constructor( + private readonly qService: QService, + private readonly cloudService: CloudService, + private readonly deviceService: DeviceService, + private readonly db: DbService, + private readonly socketService: SocketService + ) { + } + + @Process('validation') + async validation(job: Job) { + const deviceId = await this.db.getDevice(hash(job.data.metadata.sn)); + const logData = { + error: { + log: { + metadata: job.data.metadata, + payload: job.data.payload, + deviceId + } + }, + metadata: job.data.metadata, + time: Date.now() + }; + + + if (Object.values(job.data.payload).length > 0) { + await this.db.add(hash(job.data.metadata.sn), EVENTS.DEVICE_VALIDATION_ERROR, logData); + throw 'ERROR'; + } + + + await this.db.add(hash(job.data.metadata.sn), EVENTS.TRY_SET_DEVICE_STATUS, logData); + return this.cloudService.updateDeviceStatus(Number(deviceId)); + } + + @OnQueueCompleted() + async completed(job: Job, result) { + try { + const response = await this.deviceService.reset(job.data.metadata.ip); + console.log('DONE', job.data); + const logData = { + error: { + response + }, + metadata: job.data.metadata, + time: Date.now() + }; + this.socketService.send({ event: EVENTS.INITIATE_FACTORY_RESET }); + await this.db.add(hash(job.data.metadata.sn), EVENTS.INITIATE_FACTORY_RESET, logData); + await this.db.complete(hash(job.data.metadata.sn), logData).then(() => { + this.socketService.send({ event: EVENTS.COMPLETED }); + }); + + } catch (error) { + const logData = { + error: { + error + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.FACTORY_RESET_ERROR, logData); + this.socketService.send({ event: EVENTS.FACTORY_RESET_ERROR }); + } + + } + + @OnQueueFailed() + async fail(job: Job, error: any) { + const logData = { + error: { + log: { + metadata: job.data.metadata, + payload: job.data.payload, + error + } + }, + metadata: job.data.metadata, + time: Date.now() + }; + await this.db.add(hash(job.data.metadata.sn), EVENTS.SET_DEVICE_STATUS_FAILED, logData); + this.socketService.send({ event: EVENTS.SET_DEVICE_STATUS_FAILED }); + console.log('VALIDATION PROCESSOR FAILED', error); + } +} Index: apps/mt-api/src/app/models/data.model.ts =================================================================== diff -u --- apps/mt-api/src/app/models/data.model.ts (revision 0) +++ apps/mt-api/src/app/models/data.model.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,21 @@ +export interface RequestMetadata { + sn: string; + ip: string; +} + +export interface JobData { + payload: any; + metadata: RequestMetadata +} + +export interface LogData { + metadata: RequestMetadata, + error: any; + time: number; +} + +export enum ErrTypes { + REGISTER = 'register-device', + CERTIFICATE = 'upload-certificate', + VALIDATION = 'validation-errors' +} Index: apps/mt-api/src/app/socket/socket.module.ts =================================================================== diff -u --- apps/mt-api/src/app/socket/socket.module.ts (revision 0) +++ apps/mt-api/src/app/socket/socket.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SocketService } from './socket.service'; + +@Module({ + providers: [SocketService], + exports: [SocketService] +}) +export class SocketModule { +} Index: apps/mt-api/src/app/socket/socket.service.ts =================================================================== diff -u --- apps/mt-api/src/app/socket/socket.service.ts (revision 0) +++ apps/mt-api/src/app/socket/socket.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,47 @@ +import { + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + WebSocketGateway, + WebSocketServer +} from '@nestjs/websockets'; +import { Server } from 'socket.io'; +import { Logger } from '@nestjs/common'; + +@WebSocketGateway({ transports: ['websocket'] }) +export class SocketService implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { + private logger: Logger = new Logger('SocketService'); + + + @WebSocketServer() + server: Server; + + send(data: any) { + if (this.server) { + Array.from(this.server.clients).forEach((c: any) => { + c.send(JSON.stringify(data)); + }); + } + } + + get clients(): string[] { + if (this.server) { + return Array.from(this.server.clients).map((c: any) => c.id); + } + return []; + } + + afterInit(server: any): any { + this.logger.debug('WEBSOCKET INIT'); + } + + handleConnection(client: any, ...args: never[]): any { + client.id = Math.random(); + this.logger.debug(`${client.id} connected`); + } + + handleDisconnect(client: any): any { + this.logger.debug(`${client.id} disconnected`); + client.id = null; + } +} Index: apps/mt-api/src/assets/.gitkeep =================================================================== diff -u --- apps/mt-api/src/assets/.gitkeep (revision 0) +++ apps/mt-api/src/assets/.gitkeep (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ \ No newline at end of file Index: apps/mt-api/src/environments/environment.prod.ts =================================================================== diff -u --- apps/mt-api/src/environments/environment.prod.ts (revision 0) +++ apps/mt-api/src/environments/environment.prod.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; Index: apps/mt-api/src/environments/environment.ts =================================================================== diff -u --- apps/mt-api/src/environments/environment.ts (revision 0) +++ apps/mt-api/src/environments/environment.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,3 @@ +export const environment = { + production: false, +}; Index: apps/mt-api/src/main.ts =================================================================== diff -u --- apps/mt-api/src/main.ts (revision 0) +++ apps/mt-api/src/main.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,37 @@ +/** + * This is not a production server yet! + * This is only a minimal backend to get started. + */ + +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { join } from 'path'; +import { AppModule } from './app/app.module'; +import { WsAdapter } from '@nestjs/platform-ws'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import * as fs from 'fs'; + +async function bootstrap() { + const keyFile = fs.readFileSync(__dirname + '/../../../localhost.key'); + const certFile = fs.readFileSync(__dirname + '/../../../localhost.crt'); + const app = await NestFactory.create(AppModule, { + logger: true, + cors: true, + /*httpsOptions: { + key: keyFile, + cert: certFile, + }*/ + }); + + const globalPrefix = ''; + app.setGlobalPrefix(globalPrefix); + app.useStaticAssets(join(__dirname, '..', 'mt-client')); + app.useWebSocketAdapter(new WsAdapter(app)); + const port = 3000; + await app.listen(port, () => { + Logger.log('Listening at http://localhost:' + port + '/'); + Logger.log(join(__dirname, '..', 'mt-client')); + }); +} + +bootstrap(); Index: apps/mt-api/tsconfig.app.json =================================================================== diff -u --- apps/mt-api/tsconfig.app.json (revision 0) +++ apps/mt-api/tsconfig.app.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2015" + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} Index: apps/mt-api/tsconfig.json =================================================================== diff -u --- apps/mt-api/tsconfig.json (revision 0) +++ apps/mt-api/tsconfig.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} Index: apps/mt-api/tsconfig.spec.json =================================================================== diff -u --- apps/mt-api/tsconfig.spec.json (revision 0) +++ apps/mt-api/tsconfig.spec.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["**/*.spec.ts", "**/*.d.ts"] +} Index: apps/mt-client-e2e/.eslintrc.json =================================================================== diff -u --- apps/mt-client-e2e/.eslintrc.json (revision 0) +++ apps/mt-client-e2e/.eslintrc.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,20 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": "apps/mt-client-e2e/tsconfig.*?.json" + }, + "rules": {} + }, + { + "files": ["src/plugins/index.js"], + "rules": { + "@typescript-eslint/no-var-requires": "off", + "no-undef": "off" + } + } + ] +} Index: apps/mt-client-e2e/cypress.json =================================================================== diff -u --- apps/mt-client-e2e/cypress.json (revision 0) +++ apps/mt-client-e2e/cypress.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +{ + "fileServerFolder": ".", + "fixturesFolder": "./src/fixtures", + "integrationFolder": "./src/integration", + "modifyObstructiveCode": false, + "pluginsFile": "./src/plugins/index", + "supportFile": "./src/support/index.ts", + "video": true, + "videosFolder": "../../dist/cypress/apps/mt-client-e2e/videos", + "screenshotsFolder": "../../dist/cypress/apps/mt-client-e2e/screenshots", + "chromeWebSecurity": false +} Index: apps/mt-client-e2e/src/fixtures/example.json =================================================================== diff -u --- apps/mt-client-e2e/src/fixtures/example.json (revision 0) +++ apps/mt-client-e2e/src/fixtures/example.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} Index: apps/mt-client-e2e/src/integration/app.spec.ts =================================================================== diff -u --- apps/mt-client-e2e/src/integration/app.spec.ts (revision 0) +++ apps/mt-client-e2e/src/integration/app.spec.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('mt-client', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome to mt-client!'); + }); +}); Index: apps/mt-client-e2e/src/plugins/index.js =================================================================== diff -u --- apps/mt-client-e2e/src/plugins/index.js (revision 0) +++ apps/mt-client-e2e/src/plugins/index.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,22 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); + +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + + // Preprocess Typescript file using Nx helper + on('file:preprocessor', preprocessTypescript(config)); +}; Index: apps/mt-client-e2e/src/support/app.po.ts =================================================================== diff -u --- apps/mt-client-e2e/src/support/app.po.ts (revision 0) +++ apps/mt-client-e2e/src/support/app.po.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); Index: apps/mt-client-e2e/src/support/commands.ts =================================================================== diff -u --- apps/mt-client-e2e/src/support/commands.ts (revision 0) +++ apps/mt-client-e2e/src/support/commands.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) Index: apps/mt-client-e2e/src/support/index.ts =================================================================== diff -u --- apps/mt-client-e2e/src/support/index.ts (revision 0) +++ apps/mt-client-e2e/src/support/index.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; Index: apps/mt-client-e2e/tsconfig.e2e.json =================================================================== diff -u --- apps/mt-client-e2e/tsconfig.e2e.json (revision 0) +++ apps/mt-client-e2e/tsconfig.e2e.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js"] +} Index: apps/mt-client-e2e/tsconfig.json =================================================================== diff -u --- apps/mt-client-e2e/tsconfig.json (revision 0) +++ apps/mt-client-e2e/tsconfig.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.e2e.json" + } + ] +} Index: apps/mt-client/.browserslistrc =================================================================== diff -u --- apps/mt-client/.browserslistrc (revision 0) +++ apps/mt-client/.browserslistrc (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,17 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. Index: apps/mt-client/.eslintrc.json =================================================================== diff -u --- apps/mt-client/.eslintrc.json (revision 0) +++ apps/mt-client/.eslintrc.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,33 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "plugin:@nrwl/nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "parserOptions": { "project": ["apps/mt-client/tsconfig.*?.json"] }, + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { "type": "attribute", "prefix": "diaManTools", "style": "camelCase" } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "dia-man-tools", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nrwl/nx/angular-template"], + "rules": {} + } + ] +} Index: apps/mt-client/jest.config.js =================================================================== diff -u --- apps/mt-client/jest.config.js (revision 0) +++ apps/mt-client/jest.config.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,23 @@ +module.exports = { + displayName: 'mt-client', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + astTransformers: { + before: [ + 'jest-preset-angular/build/InlineFilesTransformer', + 'jest-preset-angular/build/StripStylesTransformer', + ], + }, + }, + }, + coverageDirectory: '../../coverage/apps/mt-client', + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; Index: apps/mt-client/src/app/api.service.ts =================================================================== diff -u --- apps/mt-client/src/app/api.service.ts (revision 0) +++ apps/mt-client/src/app/api.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,40 @@ +import { Inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ID } from '@datorama/akita'; +import { CFG } from './const'; +import { BaseApi } from './core/base-api.class'; +import { tap } from 'rxjs/operators'; +import { DevicesStore } from './state/devices.store'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiService extends BaseApi { + constructor( + @Inject(CFG) protected cfg: any, + private readonly http: HttpClient, + private deviceStore: DevicesStore + ) { + super(); + } + + devices() { + return this.http.get(`${this.endpoint}/client/devices`); + } + + logs(sn: ID) { + return this.http.get(`${this.endpoint}/client/logs/${sn}`).pipe( + tap(logs => { + this.deviceStore.update({ logs: Object.keys(logs).map((key) => ({ ...JSON.parse(logs[key]), type: key })) }); + }) + ); + } + + deleteDeviceLog(sn: ID) { + return this.http.delete(`${this.endpoint}/client/delete/${sn}`); + } + + deleteDevice(id: number) { + return this.http.delete(`${this.endpoint}/client/delete/device/${id}`); + } +} Index: apps/mt-client/src/app/app-routing.module.ts =================================================================== diff -u --- apps/mt-client/src/app/app-routing.module.ts (revision 0) +++ apps/mt-client/src/app/app-routing.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) }, + { path: 'completed', loadChildren: () => import('./history-log/history-log.module').then(m => m.HistoryLogModule) }, + { path: '', redirectTo: 'dashboard', pathMatch: 'full' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { useHash: true })], + exports: [RouterModule] +}) +export class AppRoutingModule { +} Index: apps/mt-client/src/app/app.component.html =================================================================== diff -u --- apps/mt-client/src/app/app.component.html (revision 0) +++ apps/mt-client/src/app/app.component.html (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,20 @@ + + +
+ +
+ + +
+ + Index: apps/mt-client/src/app/app.component.scss =================================================================== diff -u --- apps/mt-client/src/app/app.component.scss (revision 0) +++ apps/mt-client/src/app/app.component.scss (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,3 @@ +.spacer { + flex: 1 1 auto; +} Index: apps/mt-client/src/app/app.component.spec.ts =================================================================== diff -u --- apps/mt-client/src/app/app.component.spec.ts (revision 0) +++ apps/mt-client/src/app/app.component.spec.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,31 @@ +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AppComponent], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'mt-client'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('mt-client'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain( + 'Welcome to mt-client!' + ); + }); +}); Index: apps/mt-client/src/app/app.component.ts =================================================================== diff -u --- apps/mt-client/src/app/app.component.ts (revision 0) +++ apps/mt-client/src/app/app.component.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,35 @@ +import { Component, OnInit } from '@angular/core'; +import { SocketService } from './socket.service'; +import { Subject } from 'rxjs'; +import { AuthService } from './auth/auth.service'; +import { eventOfType } from './const'; +import { map, startWith } from 'rxjs/operators'; + +@Component({ + selector: 'dia-man-tools-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + + getLogsSubj = new Subject(); + isLogged = this.auth.isLogged; + clientsCount$ = this.socket.socket.pipe( + eventOfType('CONNECTED_CLIENTS'), + map(({ payload }) => payload.length || 0), + startWith(1) + ); + + constructor( + private readonly socket: SocketService, + private readonly auth: AuthService + ) { + } + + logout() { + this.auth.logout(); + } + + ngOnInit(): void { + } +} Index: apps/mt-client/src/app/app.module.ts =================================================================== diff -u --- apps/mt-client/src/app/app.module.ts (revision 0) +++ apps/mt-client/src/app/app.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,54 @@ +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { HttpClientModule } from '@angular/common/http'; +import { AkitaNgDevtools } from '@datorama/akita-ngdevtools'; +import { environment } from '../environments/environment'; +import { MatListModule } from '@angular/material/list'; +import { CFG } from './const'; +import { CoreModule } from './core/core.module'; +import { MatButtonModule } from '@angular/material/button'; +import { AuthService } from './auth/auth.service'; +import { AuthModule } from './auth/auth.module'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { AppRoutingModule } from './app-routing.module'; +import { MatTabsModule } from '@angular/material/tabs'; + +@NgModule({ + declarations: [AppComponent], + imports: [ + BrowserModule, + CoreModule, + AuthModule, + BrowserAnimationsModule, + HttpClientModule, + MatToolbarModule, + environment.production ? [] : AkitaNgDevtools.forRoot(), + MatListModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + AppRoutingModule, + MatTabsModule + ], + providers: [ + { provide: CFG, useValue: environment }, + { + provide: APP_INITIALIZER, + useFactory: (auth: AuthService) => { + return () => auth.login(); + }, + deps: [AuthService], + multi: true + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { +} Index: apps/mt-client/src/app/auth/auth.module.ts =================================================================== diff -u --- apps/mt-client/src/app/auth/auth.module.ts (revision 0) +++ apps/mt-client/src/app/auth/auth.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OAuthModule } from 'angular-oauth2-oidc'; + +export const oAuthModuleWithProviders = OAuthModule.forRoot(); + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + oAuthModuleWithProviders + ] +}) +export class AuthModule { } Index: apps/mt-client/src/app/auth/auth.service.ts =================================================================== diff -u --- apps/mt-client/src/app/auth/auth.service.ts (revision 0) +++ apps/mt-client/src/app/auth/auth.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,28 @@ +import { Inject, Injectable } from '@angular/core'; +import { CFG } from '../const'; +import { OAuthService } from 'angular-oauth2-oidc'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor( + @Inject(CFG) private readonly cfg: any, + private readonly auth: OAuthService + ) { + } + + get isLogged(): boolean { + return this.auth.hasValidAccessToken(); + } + + async login() { + this.auth.configure(this.cfg.auth); + const token = await this.auth.loadDiscoveryDocumentAndLogin().then(() => this.auth.getAccessToken()); + } + + logout(): void { + this.auth.logOut(); + } +} Index: apps/mt-client/src/app/const.ts =================================================================== diff -u --- apps/mt-client/src/app/const.ts (revision 0) +++ apps/mt-client/src/app/const.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,31 @@ +import { InjectionToken } from '@angular/core'; +import { OperatorFunction } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +export const CFG = new InjectionToken('CFG'); + +export function eventOfType(...allowedEvents: Array): OperatorFunction { + return filter((eventObject: any) => { + return allowedEvents.includes(eventObject.event); + }); +} + + +export enum EVENTS { + TRY_REGISTER = 'TRY_REGISTER', + REGISTER_ERROR = 'REGISTER_ERROR', + REGISTER_COMPLETE = 'REGISTER_COMPLETE', + TRY_GET_CERTIFICATE = 'TRY_GET_CERTIFICATE', + TRY_SET_DEVICE_STATUS = 'TRY_SET_DEVICE_STATUS', + CERTIFICATE_ERROR = 'CERTIFICATE_ERROR', + SET_CERTIFICATE = 'SET_CERTIFICATE', + CONNECTIVITY_TEST = 'CONNECTIVITY_TEST', + CONNECTIVITY_TEST_ERROR = 'CONNECTIVITY_TEST_ERROR', + SET_CERTIFICATE_ERROR = 'SET_CERTIFICATE_ERROR', + INITIATE_FACTORY_RESET = 'INITIATE_FACTORY_RESET', + FACTORY_RESET_ERROR = 'FACTORY_RESET_ERROR', + SET_DEVICE_STATUS_FAILED = 'SET_DEVICE_STATUS_FAILED', + DEVICE_VALIDATION_ERROR = 'DEVICE_VALIDATION_ERROR', + CONNECTED_CLIENTS = 'CONNECTED_CLIENTS', + COMPLETED = 'COMPLETED' +} Index: apps/mt-client/src/app/core/base-api.class.ts =================================================================== diff -u --- apps/mt-client/src/app/core/base-api.class.ts (revision 0) +++ apps/mt-client/src/app/core/base-api.class.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,7 @@ +export class BaseApi { + protected cfg: any; + + get endpoint() { + return this.cfg.api; + } +} Index: apps/mt-client/src/app/core/core.module.ts =================================================================== diff -u --- apps/mt-client/src/app/core/core.module.ts (revision 0) +++ apps/mt-client/src/app/core/core.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + + + +@NgModule({ + declarations: [], + imports: [ + CommonModule + ] +}) +export class CoreModule { } Index: apps/mt-client/src/app/dashboard/dashboard-routing.module.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/dashboard-routing.module.ts (revision 0) +++ apps/mt-client/src/app/dashboard/dashboard-routing.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { PageComponent } from './page/page.component'; + +const routes: Routes = [ + { path: '', component: PageComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class DashboardRoutingModule { +} Index: apps/mt-client/src/app/dashboard/dashboard.module.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/dashboard.module.ts (revision 0) +++ apps/mt-client/src/app/dashboard/dashboard.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { DashboardRoutingModule } from './dashboard-routing.module'; +import { PageComponent } from './page/page.component'; +import { MatTableModule } from '@angular/material/table'; +import { MatButtonModule } from '@angular/material/button'; +import { MatToolbarModule } from '@angular/material/toolbar'; + + +@NgModule({ + declarations: [ + PageComponent + ], + imports: [ + CommonModule, + DashboardRoutingModule, + MatTableModule, + MatButtonModule, + MatToolbarModule + ] +}) +export class DashboardModule { } Index: apps/mt-client/src/app/dashboard/page/page.component.css =================================================================== diff -u --- apps/mt-client/src/app/dashboard/page/page.component.css (revision 0) +++ apps/mt-client/src/app/dashboard/page/page.component.css (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,31 @@ +table { + width: 100%; +} + + +tr.logs-row { + height: 0; +} + +.logs-row td { + border-bottom-width: 0; +} + +.logs { + max-width: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} + + +.actions { + text-align: right; +} + +pre.code { + white-space: pre-wrap; /* css-3 */ + background: #000; + color: white; + font-size: 75%; +} Index: apps/mt-client/src/app/dashboard/page/page.component.html =================================================================== diff -u --- apps/mt-client/src/app/dashboard/page/page.component.html (revision 0) +++ apps/mt-client/src/app/dashboard/page/page.component.html (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
SN {{row.sn}} DEVICE IP {{row.ip}} TIME {{row.time | date:'medium'}} +
+ + + +
    +
  • +

    {{log.type}} {{log.time|date:'medium'}}

    +
    {{log.error | json}}
    +
  • +
+
+
Index: apps/mt-client/src/app/dashboard/page/page.component.spec.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/page/page.component.spec.ts (revision 0) +++ apps/mt-client/src/app/dashboard/page/page.component.spec.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageComponent } from './page.component'; + +describe('PageComponent', () => { + let component: PageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); Index: apps/mt-client/src/app/dashboard/page/page.component.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/page/page.component.ts (revision 0) +++ apps/mt-client/src/app/dashboard/page/page.component.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,84 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {DeviceService} from '../state/device.service'; +import {filter, map, startWith, switchMap, tap} from 'rxjs/operators'; +import {DeviceQuery} from '../state/device.query'; +import {noop, Observable, Subscription} from 'rxjs'; +import {Log} from '../state/device.model'; +import {animate, state, style, transition, trigger} from '@angular/animations'; +import {isNil} from '@datorama/akita'; +import {SocketService} from '../../socket.service'; +import {eventOfType, EVENTS} from '../../const'; + +@Component({ + selector: 'dia-man-tools-page', + templateUrl: './page.component.html', + styleUrls: ['./page.component.css'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({height: '0px', minHeight: '0'})), + state('expanded', style({height: '*'})), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')) + ]) + ] +}) +export class PageComponent implements OnInit, OnDestroy { + private readonly subs = new Subscription(); + devices$ = this.deviceQuery.selectAll(); + logs$: Observable = this.deviceQuery.selectActiveId().pipe( + switchMap(id => this.deviceQuery.select('logs').pipe(map(logs => logs[id]))) + ); + + displayedColumns = ['sn', 'ip', 'time']; + + constructor( + private readonly deviceQuery: DeviceQuery, + private readonly deviceService: DeviceService, + private readonly socketService: SocketService + ) { + } + + logs(sn: string) { + const hasActive = this.deviceQuery.hasActive(sn); + this.deviceService.setActive(hasActive ? null : sn); + } + + get activeId() { + return this.deviceQuery.getActiveId(); + } + + delete(sn: string) { + this.deviceService.delete(sn).toPromise().then(noop); + } + + ngOnInit(): void { + //REGISTER_COMPLETE + this.subs.add( + this.socketService.socket.pipe( + eventOfType( + EVENTS.REGISTER_ERROR, + EVENTS.FACTORY_RESET_ERROR, + EVENTS.CERTIFICATE_ERROR, + EVENTS.CONNECTIVITY_TEST_ERROR, + EVENTS.SET_CERTIFICATE_ERROR, + EVENTS.DEVICE_VALIDATION_ERROR + ), + startWith(true), + switchMap(() => this.deviceService.fetchDevices()) + ).subscribe() + ); + + this.subs.add( + this.deviceQuery.selectActiveId().pipe( + tap(console.log), + filter(active => !isNil(active)), + switchMap(sn => this.deviceService.fetchLogs(sn)) + ).subscribe() + ); + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); + } + + +} Index: apps/mt-client/src/app/dashboard/state/device.model.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/state/device.model.ts (revision 0) +++ apps/mt-client/src/app/dashboard/state/device.model.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +export interface Device { + sn: string; + ip: string; + time: number; +} + + +export interface Log { + time: number; + log: any; + type: string; +} Index: apps/mt-client/src/app/dashboard/state/device.query.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/state/device.query.ts (revision 0) +++ apps/mt-client/src/app/dashboard/state/device.query.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { QueryEntity } from '@datorama/akita'; +import { DeviceState, DeviceStore } from './device.store'; + +@Injectable({ providedIn: 'root' }) +export class DeviceQuery extends QueryEntity { + + constructor(protected store: DeviceStore) { + super(store); + } + +} Index: apps/mt-client/src/app/dashboard/state/device.service.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/state/device.service.ts (revision 0) +++ apps/mt-client/src/app/dashboard/state/device.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,41 @@ +import {Inject, Injectable} from '@angular/core'; +import {DeviceStore} from './device.store'; +import {BaseApi} from '../../core/base-api.class'; +import {HttpClient} from '@angular/common/http'; +import {CFG} from '../../const'; +import {tap} from 'rxjs/operators'; +import {Device, Log} from './device.model'; + +@Injectable({providedIn: 'root'}) +export class DeviceService extends BaseApi { + + constructor( + @Inject(CFG) protected cfg: any, + private deviceStore: DeviceStore, + private readonly http: HttpClient + ) { + super(); + } + + setActive(sn: string): void { + this.deviceStore.setActive(sn); + } + + fetchDevices() { + return this.http.get(`${this.endpoint}/client/devices`).pipe( + tap((devices) => this.deviceStore.set(devices)) + ); + } + + fetchLogs(sn: string) { + return this.http.get(`${this.endpoint}/client/logs/${sn}`).pipe( + tap(logs => this.deviceStore.update({logs: {[sn]: logs}})) + ); + } + + delete(sn: string) { + return this.http.delete(`${this.endpoint}/client/delete/device/${sn}`).pipe( + tap(() => this.deviceStore.remove(sn)) + ); + } +} Index: apps/mt-client/src/app/dashboard/state/device.store.ts =================================================================== diff -u --- apps/mt-client/src/app/dashboard/state/device.store.ts (revision 0) +++ apps/mt-client/src/app/dashboard/state/device.store.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { EntityState, EntityStore, StoreConfig } from '@datorama/akita'; +import { Device, Log } from './device.model'; + +export interface DeviceState extends EntityState { + logs: { [key: string]: Log[] | any }; +} + +@Injectable({ providedIn: 'root' }) +@StoreConfig({ name: 'Device', idKey: 'sn' }) +export class DeviceStore extends EntityStore { + + constructor() { + super({ logs: {} }); + } +} Index: apps/mt-client/src/app/history-log/history-log-routing.module.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/history-log-routing.module.ts (revision 0) +++ apps/mt-client/src/app/history-log/history-log-routing.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { PageComponent } from './page/page.component'; + +const routes: Routes = [ + { path: '', component: PageComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HistoryLogRoutingModule { +} Index: apps/mt-client/src/app/history-log/history-log.module.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/history-log.module.ts (revision 0) +++ apps/mt-client/src/app/history-log/history-log.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { HistoryLogRoutingModule } from './history-log-routing.module'; +import { PageComponent } from './page/page.component'; +import { MatTableModule } from '@angular/material/table'; +import { SharedModule } from '../shared/shared.module'; +import { MatListModule } from '@angular/material/list'; + + +@NgModule({ + declarations: [ + PageComponent + ], + imports: [ + CommonModule, + HistoryLogRoutingModule, + MatTableModule, + SharedModule, + MatListModule + ] +}) +export class HistoryLogModule { } Index: apps/mt-client/src/app/history-log/page/page.component.css =================================================================== diff -u --- apps/mt-client/src/app/history-log/page/page.component.css (revision 0) +++ apps/mt-client/src/app/history-log/page/page.component.css (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,25 @@ +table { + width: 100%; +} + + +tr.logs-row { + height: 0; +} + +tr.logs-row:not(.expanded-row):hover { + background: whitesmoke; +} + +tr.logs-row:not(.expanded-row):active { + background: #efefef; +} + +.logs-row td { + border-bottom-width: 0; +} + +.logs { + overflow: hidden; + display: flex; +} Index: apps/mt-client/src/app/history-log/page/page.component.html =================================================================== diff -u --- apps/mt-client/src/app/history-log/page/page.component.html (revision 0) +++ apps/mt-client/src/app/history-log/page/page.component.html (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SN {{row.sn}} DEVICE IP {{row.ip}} TIME {{row.time | date:'medium'}} DEVICE #ID {{row.deviceId}} +
+ + +

{{log.type}}

+

{{log.time | date:'medium'}}

+
+
+
+
Index: apps/mt-client/src/app/history-log/page/page.component.spec.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/page/page.component.spec.ts (revision 0) +++ apps/mt-client/src/app/history-log/page/page.component.spec.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageComponent } from './page.component'; + +describe('PageComponent', () => { + let component: PageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); Index: apps/mt-client/src/app/history-log/page/page.component.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/page/page.component.ts (revision 0) +++ apps/mt-client/src/app/history-log/page/page.component.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,55 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {HistoryService} from '../state/history.service'; +import {HistoryQuery} from '../state/history.query'; +import {animate, state, style, transition, trigger} from '@angular/animations'; +import {SocketService} from '../../socket.service'; +import {eventOfType, EVENTS} from '../../const'; +import {startWith, switchMap} from 'rxjs/operators'; +import {Order} from '@datorama/akita'; +import {Subscription} from "rxjs"; + +@Component({ + selector: 'dia-man-tools-page', + templateUrl: './page.component.html', + styleUrls: ['./page.component.css'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({height: '0px', minHeight: '0'})), + state('expanded', style({height: '*'})), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')) + ]) + ] +}) +export class PageComponent implements OnInit, OnDestroy { + private readonly subs = new Subscription(); + devices$ = this.historyQuery.selectAll({ + sortBy: 'time', + sortByOrder: Order.DESC + }); + displayedColumns = ['sn', 'ip', 'time', 'deviceId']; + expandedElement = null; + + constructor( + private readonly historyQuery: HistoryQuery, + private readonly historyService: HistoryService, + private readonly socket: SocketService + ) { + } + + ngOnInit(): void { + this.subs.add( + this.socket.socket.pipe( + eventOfType(EVENTS.COMPLETED), + startWith(true), + switchMap(() => this.historyService.fetchAll()) + ) + .subscribe(console.log) + ); + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); + } + + +} Index: apps/mt-client/src/app/history-log/state/history.model.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/state/history.model.ts (revision 0) +++ apps/mt-client/src/app/history-log/state/history.model.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,7 @@ +export interface CompletedDevice { + sn: string; + ip: string; + time: number; + deviceId: number; + log: any; +} Index: apps/mt-client/src/app/history-log/state/history.query.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/state/history.query.ts (revision 0) +++ apps/mt-client/src/app/history-log/state/history.query.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { QueryEntity } from '@datorama/akita'; +import { HistoryStore, HistoryState } from './history.store'; + +@Injectable({ providedIn: 'root' }) +export class HistoryQuery extends QueryEntity { + + constructor(protected store: HistoryStore) { + super(store); + } + +} Index: apps/mt-client/src/app/history-log/state/history.service.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/state/history.service.ts (revision 0) +++ apps/mt-client/src/app/history-log/state/history.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,26 @@ +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { HistoryStore } from './history.store'; +import { CFG } from '../../const'; +import { BaseApi } from '../../core/base-api.class'; +import { tap } from 'rxjs/operators'; + +@Injectable({ providedIn: 'root' }) +export class HistoryService extends BaseApi { + + constructor( + @Inject(CFG) protected cfg: any, + private historyStore: HistoryStore, + private http: HttpClient + ) { + super(); + } + + + fetchAll() { + return this.http.get(`${this.endpoint}/client/devices/completed`).pipe( + tap(data => this.historyStore.set(data)) + ); + } + +} Index: apps/mt-client/src/app/history-log/state/history.store.ts =================================================================== diff -u --- apps/mt-client/src/app/history-log/state/history.store.ts (revision 0) +++ apps/mt-client/src/app/history-log/state/history.store.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { EntityState, EntityStore, StoreConfig } from '@datorama/akita'; +import { CompletedDevice } from './history.model'; + +export interface HistoryState extends EntityState { +} + +@Injectable({ providedIn: 'root' }) +@StoreConfig({ name: 'History', idKey: 'sn' }) +export class HistoryStore extends EntityStore { + + constructor() { + super(); + } + +} Index: apps/mt-client/src/app/shared/logs.pipe.ts =================================================================== diff -u --- apps/mt-client/src/app/shared/logs.pipe.ts (revision 0) +++ apps/mt-client/src/app/shared/logs.pipe.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Log } from '../dashboard/state/device.model'; + +@Pipe({ + name: 'logs' +}) +export class LogsPipe implements PipeTransform { + + transform(value: Log[]): any[] { + if (value) { + return [...value].sort((a, b) => a.time - b.time); + } + return []; + } + +} Index: apps/mt-client/src/app/shared/shared.module.ts =================================================================== diff -u --- apps/mt-client/src/app/shared/shared.module.ts (revision 0) +++ apps/mt-client/src/app/shared/shared.module.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LogsPipe } from './logs.pipe'; + + + +@NgModule({ + declarations: [ + LogsPipe + ], + imports: [ + CommonModule + ], + exports: [ + LogsPipe + ] +}) +export class SharedModule { } Index: apps/mt-client/src/app/socket.service.ts =================================================================== diff -u --- apps/mt-client/src/app/socket.service.ts (revision 0) +++ apps/mt-client/src/app/socket.service.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { webSocket } from 'rxjs/webSocket'; +import { environment } from '../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class SocketService { + socket = webSocket(environment.ws); + + constructor() { + } +} Index: apps/mt-client/src/assets/.gitkeep =================================================================== diff -u --- apps/mt-client/src/assets/.gitkeep (revision 0) +++ apps/mt-client/src/assets/.gitkeep (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ \ No newline at end of file Index: apps/mt-client/src/environments/environment.int.ts =================================================================== diff -u --- apps/mt-client/src/environments/environment.int.ts (revision 0) +++ apps/mt-client/src/environments/environment.int.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,15 @@ +export const environment = { + production: true, + api: 'https://mft.diality.integration.kebormed.com', + ws: 'wss://mft.diality.integration.kebormed.com', + auth: { + issuer: 'https://identity.diality.integration.kebormed.com/auth/realms/Main', + redirectUri: 'https://mft.diality.integration.kebormed.com', + silentRefreshRedirectUri: 'https://mft.diality.integration.kebormed.com/silent-refresh.html', + clientId: 'frontend-client', + requireHttps: false, + scope: 'openid profile email offline_access', + responseType: 'code', + disableAtHashCheck: true + } +}; Index: apps/mt-client/src/environments/environment.prod.ts =================================================================== diff -u --- apps/mt-client/src/environments/environment.prod.ts (revision 0) +++ apps/mt-client/src/environments/environment.prod.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,15 @@ +export const environment = { + production: true, + api: 'https://mft.diality.integration.kebormed.com', + ws: 'wss://mft.diality.integration.kebormed.com', + auth: { + issuer: 'https://identity.diality.integration.kebormed.com/auth/realms/Main', + redirectUri: 'https://mft.diality.integration.kebormed.com', + silentRefreshRedirectUri: 'https://mft.diality.integration.kebormed.com/silent-refresh.html', + clientId: 'frontend-client', + requireHttps: false, + scope: 'openid profile email offline_access', + responseType: 'code', + disableAtHashCheck: true + } +}; Index: apps/mt-client/src/environments/environment.ts =================================================================== diff -u --- apps/mt-client/src/environments/environment.ts (revision 0) +++ apps/mt-client/src/environments/environment.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,28 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + api: 'http://localhost:3000', + ws: 'ws://localhost:3000', + auth: { + issuer: 'https://identity.diality.integration.kebormed.com/auth/realms/Main', + redirectUri: 'http://localhost:4200', + silentRefreshRedirectUri: 'http://localhost:4200/silent-refresh.html', + clientId: 'frontend-client', + requireHttps: false, + scope: 'openid profile email offline_access', + responseType: 'code', + disableAtHashCheck: true, + }, +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. Index: apps/mt-client/src/favicon.ico =================================================================== diff -u Binary files differ Index: apps/mt-client/src/index.html =================================================================== diff -u --- apps/mt-client/src/index.html (revision 0) +++ apps/mt-client/src/index.html (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ + + + + + MtClient + + + + + + + + + + + Index: apps/mt-client/src/main.ts =================================================================== diff -u --- apps/mt-client/src/main.ts (revision 0) +++ apps/mt-client/src/main.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,13 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.error(err)); Index: apps/mt-client/src/polyfills.ts =================================================================== diff -u --- apps/mt-client/src/polyfills.ts (revision 0) +++ apps/mt-client/src/polyfills.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,64 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * IE11 requires the following for NgClass support on SVG elements + */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ Index: apps/mt-client/src/styles.scss =================================================================== diff -u --- apps/mt-client/src/styles.scss (revision 0) +++ apps/mt-client/src/styles.scss (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,39 @@ + +// Custom Theming for Angular Material +// For more information: https://material.angular.io/guide/theming +@use '~@angular/material' as mat; +// Plus imports for other components in your app. + +// Include the common styles for Angular Material. We include this here so that you only +// have to load a single css file for Angular Material in your app. +// Be sure that you only ever include this mixin once! +@include mat.core(); + +// Define the palettes for your theme using the Material Design palettes available in palette.scss +// (imported above). For each palette, you can optionally specify a default, lighter, and darker +// hue. Available color palettes: https://material.io/design/color/ +$mt-client-primary: mat.define-palette(mat.$indigo-palette); +$mt-client-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); + +// The warn palette is optional (defaults to red). +$mt-client-warn: mat.define-palette(mat.$red-palette); + +// Create the theme object. A theme consists of configurations for individual +// theming systems such as "color" or "typography". +$mt-client-theme: mat.define-light-theme(( + color: ( + primary: $mt-client-primary, + accent: $mt-client-accent, + warn: $mt-client-warn, + ) +)); + +// Include theme styles for core and each component used in your app. +// Alternatively, you can import and @include the theme mixins for each component +// that you are using. +@include mat.all-component-themes($mt-client-theme); + +/* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } Index: apps/mt-client/src/test-setup.ts =================================================================== diff -u --- apps/mt-client/src/test-setup.ts (revision 0) +++ apps/mt-client/src/test-setup.ts (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ +import 'jest-preset-angular/setup-jest'; Index: apps/mt-client/tsconfig.app.json =================================================================== diff -u --- apps/mt-client/tsconfig.app.json (revision 0) +++ apps/mt-client/tsconfig.app.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [] + }, + "files": ["src/main.ts", "src/polyfills.ts"], + "include": ["src/**/*.d.ts"] +} Index: apps/mt-client/tsconfig.editor.json =================================================================== diff -u --- apps/mt-client/tsconfig.editor.json (revision 0) +++ apps/mt-client/tsconfig.editor.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.ts"], + "compilerOptions": { + "types": ["jest", "node"] + } +} Index: apps/mt-client/tsconfig.json =================================================================== diff -u --- apps/mt-client/tsconfig.json (revision 0) +++ apps/mt-client/tsconfig.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./tsconfig.editor.json" + } + ] +} Index: apps/mt-client/tsconfig.spec.json =================================================================== diff -u --- apps/mt-client/tsconfig.spec.json (revision 0) +++ apps/mt-client/tsconfig.spec.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} Index: decorate-angular-cli.js =================================================================== diff -u --- decorate-angular-cli.js (revision 0) +++ decorate-angular-cli.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,69 @@ +/** + * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching + * and faster execution of tasks. + * + * It does this by: + * + * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. + * - Symlinking the ng to nx command, so all commands run through the Nx CLI + * - Updating the package.json postinstall script to give you control over this script + * + * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. + * Every command you run should work the same when using the Nx CLI, except faster. + * + * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, + * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. + * The Nx CLI simply does some optimizations before invoking the Angular CLI. + * + * To opt out of this patch: + * - Replace occurrences of nx with ng in your package.json + * - Remove the script from your postinstall script in your package.json + * - Delete and reinstall your node_modules + */ + +const fs = require('fs'); +const os = require('os'); +const cp = require('child_process'); +const isWindows = os.platform() === 'win32'; +let output; +try { + output = require('@nrwl/workspace').output; +} catch (e) { + console.warn('Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.'); + process.exit(0); +} + +/** + * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still + * invoke the Nx CLI and get the benefits of computation caching. + */ +function symlinkNgCLItoNxCLI() { + try { + const ngPath = './node_modules/.bin/ng'; + const nxPath = './node_modules/.bin/nx'; + if (isWindows) { + /** + * This is the most reliable way to create symlink-like behavior on Windows. + * Such that it works in all shells and works with npx. + */ + ['', '.cmd', '.ps1'].forEach(ext => { + if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); + }); + } else { + // If unix-based, symlink + cp.execSync(`ln -sf ./nx ${ngPath}`); + } + } + catch(e) { + output.error({ title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message }); + throw e; + } +} + +try { + symlinkNgCLItoNxCLI(); + require('@nrwl/cli/lib/decorate-cli').decorateCli(); + output.log({ title: 'Angular CLI has been decorated to enable computation caching.' }); +} catch(e) { + output.error({ title: 'Decoration of the Angular CLI did not complete successfully' }); +} Index: ecosystem.config.js =================================================================== diff -u --- ecosystem.config.js (revision 0) +++ ecosystem.config.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,22 @@ +module.exports = { + apps: [{ + script: 'index.js', + watch: '.' + }, { + script: './service-worker/', + watch: ['./service-worker'] + }], + + deploy: { + production: { + user: 'SSH_USERNAME', + host: 'SSH_HOSTMACHINE', + ref: 'origin/master', + repo: 'GIT_REPOSITORY', + path: 'DESTINATION_PATH', + 'pre-deploy-local': '', + 'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production', + 'pre-setup': '' + } + } +}; Index: jest.config.js =================================================================== diff -u --- jest.config.js (revision 0) +++ jest.config.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,3 @@ +module.exports = { + projects: ['/apps/mt-client', '/apps/mt-api'], +}; Index: jest.preset.js =================================================================== diff -u --- jest.preset.js (revision 0) +++ jest.preset.js (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,3 @@ +const nxPreset = require('@nrwl/jest/preset'); + +module.exports = { ...nxPreset }; Index: localhost.crt =================================================================== diff -u --- localhost.crt (revision 0) +++ localhost.crt (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5TCCAc2gAwIBAgIJAKLoUIOujlkXMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0yMTA2MTgxNzQ3NDJaFw0yMTA3MTgxNzQ3NDJaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALOaT6xyUoLyD4gtGvvrKrCZqzHVtWDK6xj6GfjvIJrVUC4lL1RW1Vw1337p +YYvXBuKX1V+p6IK9t2el3MSu8CVKKh+JDqpmqjTWtzRS36YiUtvlKALWaVYoK6MQ +BSqP5X0HZFIUOReKhiZkUNKI9/5Ylt9ry4sFSVRGDy2YYpW4KJrzzM7z/5hn/Yv+ +1l2D7Hd9gRqeqjCssm/HcKIo2M0KQyHLKNE/8PggD5rskQW8f5ykWIk525lQW1xu +COKWLbvFLRROr+eNtlq7JjBwTh4ssDNSRlcicgMJR8PhhKbInjI1ngTKk2BMyIV6 +EswhinixF8TKK9FK+8e7FcDdTaECAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo +b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B +AQsFAAOCAQEAheSrBAeKezM2HpMZynlTfC+hagB9xnP2x63FMBBI2ZmCNh/g3lTJ +oXSYlu9pklZR8PZNYa6LuUXVxJvuSWvUwNBAV7Qcs2L83ljJ1ctoq+99YFbkYXqZ +84oZ5rre18rsQgOJuy+Ciz3RdytvwWCpDbwY8Pu3O2iQNRdLWtQU80Idwgni9d1B +qnLTLz8wBqfhN1P9SETaqUbE0esFeA9TgVnqMnvzUS8cpjGzPuEU0WapPBuLfxe/ +QuFcrphCozIhAtJ3IhrNxu9G/PYixC+TU7M7k0rpPRZBKn7MIkbRzAiEkimC0lml +c275LucobMYwQ1spIjPnxdMCh98gUKnKzQ== +-----END CERTIFICATE----- Index: localhost.key =================================================================== diff -u --- localhost.key (revision 0) +++ localhost.key (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzmk+sclKC8g+I +LRr76yqwmasx1bVgyusY+hn47yCa1VAuJS9UVtVcNd9+6WGL1wbil9VfqeiCvbdn +pdzErvAlSiofiQ6qZqo01rc0Ut+mIlLb5SgC1mlWKCujEAUqj+V9B2RSFDkXioYm +ZFDSiPf+WJbfa8uLBUlURg8tmGKVuCia88zO8/+YZ/2L/tZdg+x3fYEanqowrLJv +x3CiKNjNCkMhyyjRP/D4IA+a7JEFvH+cpFiJOduZUFtcbgjili27xS0UTq/njbZa +uyYwcE4eLLAzUkZXInIDCUfD4YSmyJ4yNZ4EypNgTMiFehLMIYp4sRfEyivRSvvH +uxXA3U2hAgMBAAECggEAehMbrmmyHaDr37JQ0gQA6zI3w1Kvys+BxmNQPfxDHBp7 +OjI6Hkg7E0bm1ztH+yW3X1K2TCSPjjXnMELzcJg7UA093ltpJXdMn0eGFMFzlFdm +HGKAtJUJ2e7J4jakXE6cPERG8ucd+xtDnVq3+LHyZeXO/qMlISQBuI86B9K4x2vW +ZoGsc2i6bmIDxRpGcvtelO6o0NJXVQeUHd9VXFz2m+dl72bkRL8FUZu2Xg+SYrWG +EXn3ox3YaUwpxk7gQ83xHE5q3So73xicr6Vwj0PvnmNDtZ0suquURaeSSxG/Qi98 +eA9rPqPAffuSnYi7tW+JIkMEQj8NBOe3VM/zc3nJAQKBgQDXur+qKsiPsgxzLa5m +i8YCOVfrOJaq8zx2Qy5edAR0QNvtbum3a7GpYDSjORMcUVTVT/FQRVhmDBj8wFmk +N0Aaypqwmk5sr9q3EHmAKQn3prHtoOyWkXUXlGhv4PB3aJgqNIEW7Zuyw7LdPseL +3IuGkWc3JUS3tnytJ71RoaEjEQKBgQDVISTwxLPQdFoRgiV/cCudv50hVjkfGWZG +UKhiqy73p3wk78bdb8Q7au0bfSF6/YTNxSOTOKjn28wkROq90BNaEjssrpF4xCW1 +Y1AIN9I0LaMUloS62+kX935ypMmgHROX+cB8Rj1TF+uBd7M0LxPRynMEZwVSnIjN +NI4m819hkQKBgBx/7WY7tLdXHFHzDQE7IfOVQ8otdaor7CbrhSqxRQ1erC9X4cR7 +k8L+lLWjiqTJEd4Ea6rhHmaM3pg1iSIqsmtXrMv79rRhHxHkOUj3Ivv2p7bu9ZKG +sONmlc2B+AKubLvWp98xkMozFGQqMQ6HGl+yweIFKGjk0N/HSjmIktMhAoGBAJui +HWiYKzqQjGj2oc1CqwcBsotyfDnigVXddmi+2ohMTJJoac6v3BsLhOOq53T5hi4A +xG6wVF8jmRnvHX/IXylJ4N5LB72pdmPThtDJen1RyiGxnHEbP0aaG0M6ZGXAddtn +rJDTddqkv/wF3d3Adveen4jadf+SgS9gLmHxHHiRAoGACZ+8pupQYPkPByC7j7ha +n+0L/bXYzS/puONb1+NIYYSJlTIQfEKnNG0LVCajm1TUQqbQ0BqjqhnFMr4BWGl0 +XuWzzDWlXPVZByn55vZX9goJ44CnNdAApVLSuiewHvqU+sVhPC999uTEKho86xBs +lkDLVi3ccUlYFuMujveooog= +-----END PRIVATE KEY----- Index: nx.json =================================================================== diff -u --- nx.json (revision 0) +++ nx.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,33 @@ +{ + "implicitDependencies": { + "package.json": { + "dependencies": "*", + "devDependencies": "*" + }, + ".eslintrc.json": "*" + }, + "affected": { + "defaultBase": "master" + }, + "npmScope": "dia-man-tools", + "tasksRunnerOptions": { + "default": { + "runner": "@nrwl/workspace/tasks-runners/default", + "options": { + "cacheableOperations": ["build", "lint", "test", "e2e"] + } + } + }, + "projects": { + "mt-client": { + "tags": [] + }, + "mt-client-e2e": { + "tags": [], + "implicitDependencies": ["mt-client"] + }, + "mt-api": { + "tags": [] + } + } +} Index: package.json =================================================================== diff -u --- package.json (revision 0) +++ package.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,104 @@ +{ + "name": "dia-man-tools", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "nx", + "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main", + "nx": "nx", + "start": "ng serve", + "start:server": "nx serve mt-api", + "start:client": "nx run mt-client:serve:development", + "build": "ng build", + "test": "ng test", + "lint": "nx workspace-lint && ng lint", + "e2e": "ng e2e", + "build:client": "nx run mt-client:build:production", + "build:api": "nx run mt-api:build:production", + "affected:apps": "nx affected:apps", + "affected:libs": "nx affected:libs", + "affected:build": "nx affected:build", + "affected:e2e": "nx affected:e2e", + "affected:test": "nx affected:test", + "affected:lint": "nx affected:lint", + "affected:dep-graph": "nx affected:dep-graph", + "affected": "nx affected", + "format": "nx format:write", + "format:write": "nx format:write", + "format:check": "nx format:check", + "update": "nx migrate latest", + "workspace-generator": "nx workspace-generator", + "dep-graph": "nx dep-graph", + "help": "nx help" + }, + "private": true, + "dependencies": { + "@angular/animations": "12.0.0", + "@angular/cdk": "^12.0.0", + "@angular/common": "12.0.0", + "@angular/compiler": "12.0.0", + "@angular/core": "12.0.0", + "@angular/forms": "12.0.0", + "@angular/material": "^12.0.0", + "@angular/platform-browser": "12.0.0", + "@angular/platform-browser-dynamic": "12.0.0", + "@angular/router": "12.0.0", + "@datorama/akita": "^6.1.3", + "@nestjs/bull": "^0.3.1", + "@nestjs/common": "^7.0.0", + "@nestjs/config": "^0.6.3", + "@nestjs/core": "^7.0.0", + "@nestjs/platform-express": "^7.0.0", + "@nestjs/platform-socket.io": "^7.6.17", + "@nestjs/platform-ws": "^7.6.17", + "@nestjs/schedule": "^0.4.3", + "@nestjs/websockets": "^7.6.17", + "@nrwl/angular": "12.3.3", + "angular-oauth2-oidc": "^10.0.3", + "bull": "^3.22.6", + "hbs": "^4.1.2", + "nestjs-redis": "^1.3.3", + "openid-client": "^5.1.5", + "reflect-metadata": "^0.1.13", + "rxjs": "~6.6.0", + "tslib": "^2.0.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "12.0.0", + "@angular-eslint/eslint-plugin": "~12.0.0", + "@angular-eslint/eslint-plugin-template": "~12.0.0", + "@angular-eslint/template-parser": "~12.0.0", + "@angular/cli": "12.0.0", + "@angular/compiler-cli": "12.0.0", + "@angular/language-service": "12.0.0", + "@datorama/akita-ngdevtools": "^6.0.0", + "@nestjs/schematics": "^7.0.0", + "@nestjs/testing": "^7.0.0", + "@nrwl/cli": "12.3.3", + "@nrwl/cypress": "12.3.3", + "@nrwl/eslint-plugin-nx": "12.3.3", + "@nrwl/jest": "12.3.3", + "@nrwl/linter": "12.3.3", + "@nrwl/nest": "^12.3.3", + "@nrwl/node": "12.3.3", + "@nrwl/tao": "12.3.3", + "@nrwl/workspace": "12.3.3", + "@types/bull": "^3.15.1", + "@types/cron": "^1.7.2", + "@types/jest": "26.0.8", + "@types/node": "14.14.33", + "@types/socket.io": "^3.0.2", + "@typescript-eslint/eslint-plugin": "4.19.0", + "@typescript-eslint/parser": "4.19.0", + "dotenv": "8.2.0", + "eslint": "7.22.0", + "eslint-config-prettier": "8.1.0", + "jest": "26.2.2", + "jest-preset-angular": "8.4.0", + "prettier": "2.2.1", + "ts-jest": "26.5.5", + "ts-node": "~9.1.1", + "typescript": "~4.2.4" + } +} Index: tools/generators/.gitkeep =================================================================== diff -u --- tools/generators/.gitkeep (revision 0) +++ tools/generators/.gitkeep (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1 @@ \ No newline at end of file Index: tools/tsconfig.tools.json =================================================================== diff -u --- tools/tsconfig.tools.json (revision 0) +++ tools/tsconfig.tools.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist/out-tsc/tools", + "rootDir": ".", + "module": "commonjs", + "target": "es5", + "types": ["node"], + "importHelpers": false + }, + "include": ["**/*.ts"] +} Index: tsconfig.base.json =================================================================== diff -u --- tsconfig.base.json (revision 0) +++ tsconfig.base.json (revision c434930351d0a3ec148b14837f65172719481858) @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "rootDir": ".", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es2015", + "module": "esnext", + "lib": ["es2017", "dom"], + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "baseUrl": ".", + "paths": {} + }, + "exclude": ["node_modules", "tmp"] +}