diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aab4ebc9..82d3163a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,6 +23,7 @@ import { MonacoEditorModule } from 'ngx-monaco-editor'; import { MatInputModule } from '@angular/material/input'; import { MatPaginatorIntl } from '@angular/material/paginator'; import { MatPaginatorIntlDa } from '@shared/helpers/mat-paginator-intl-da'; +import { MatTooltipModule } from '@angular/material/tooltip'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -62,6 +63,7 @@ export function tokenGetter() { SearchModule, HttpClientModule, MatInputModule, + MatTooltipModule, JwtModule.forRoot({ config: { tokenGetter diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index ff4a356a..7c11602f 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -66,7 +66,7 @@

Detaljer

+ [ctaLabelPrimary]="'FORM.CREATE-NEW-DATATARGET' | translate" [ctaRouterLinkPrimary]="'datatarget-new'">
diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 288804b8..2b6dfdbe 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -52,9 +52,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { label: '', editRouterLink: '../../edit-application/' + this.id, isErasable: true, - }; - - console.log(this.id); + }; } this.translate diff --git a/src/app/applications/applications-routing.module.ts b/src/app/applications/applications-routing.module.ts index d5dcd3eb..5d285e70 100644 --- a/src/app/applications/applications-routing.module.ts +++ b/src/app/applications/applications-routing.module.ts @@ -11,7 +11,7 @@ import { DatatargetDetailComponent } from './datatarget/datatarget-detail/datata import { BulkImportComponent } from './bulk-import/bulk-import.component'; import { MulticastEditComponent } from './multicast/multicast-edit/multicast-edit.component'; import { MulticastDetailComponent } from './multicast/multicast-detail/multicast-detail.component'; - +import { DatatargetNewComponent } from './datatarget/datatarget-new/datatarget-new.component'; const applicationRoutes: Routes = [ { @@ -27,12 +27,11 @@ const applicationRoutes: Routes = [ { path: '', component: ApplicationDetailComponent }, { path: 'new-iot-device', component: IotDeviceEditComponent, }, { path: 'iot-device-edit/:deviceId', component: IotDeviceEditComponent, }, - { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, - + { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, + { path: 'datatarget-new', component: DatatargetNewComponent }, { path: 'datatarget-edit', component: DatatargetEditComponent }, { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, - { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, - + { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, { path: 'multicast-edit', component: MulticastEditComponent}, { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, { path: 'multicast/:multicastId', component: MulticastDetailComponent }, @@ -41,7 +40,7 @@ const applicationRoutes: Routes = [ ], }, - ], + ], }, ]; diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts new file mode 100644 index 00000000..3a9402c7 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts @@ -0,0 +1,11 @@ +/* tslint:disable:no-unused-variable */ + +import { ViewContainerRef } from '@angular/core'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail-type-selector.directive'; +let viewContainerRef: ViewContainerRef; +describe('Directive: DatatargetDetailTypeSelector', () => { + it('should create an instance', () => { + const directive = new DatatargetDetailTypeSelectorDirective(viewContainerRef); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts new file mode 100644 index 00000000..84a3f73a --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts @@ -0,0 +1,9 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[detail-component]' +}) +export class DatatargetDetailTypeSelectorDirective { + + constructor(public viewContainerRef: ViewContainerRef) { } +} diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html index cc9829db..1c0c268c 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html @@ -1,74 +1,3 @@ -
- -
-
-
-
-

{{ 'DATATARGET.DETAILS' | translate }}

- - -

{{ 'DATATARGET.URL' | translate }}{{datatarget.url}}

-

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

-

{{ 'DATATARGET.TYPE' | translate }}{{datatarget.type | translate}}

- -

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

-
{{datatarget.authorizationHeader}}
- -

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

-
- -
-
-
-
-

{{ 'DATATARGET.OPENDATA-DK' | translate }}

-
- -
- -

{{ 'DATATARGET.NO-OPENDATA-DK' | translate }}

-
-
-
-
- -
-
-
-
-

{{ 'DATATARGET.RELATIONS' | translate }}

-
-

{{'DATATARGET.NO-RELATIONS' | translate}}

-
-
-
-
-
-

{{'DATATARGET.PAYLOADEDECODER' | translate}} - {{relation.payloadDecoder.name}} - - {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} -

-
-
- -
-
-

{{'DATATARGET.IOTDEVICE' | translate}} - - , {{device.name}} - -

-
- -
-
-
-
-
-
-
-
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts index af38e9d9..17970ff3 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts @@ -1,16 +1,11 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Component, ComponentFactoryResolver, OnDestroy, OnInit, Type, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; -import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; -import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; -import { BackButton } from '@shared/models/back-button.model'; -import { DatatargetService } from '../datatarget.service'; -import { Location } from '@angular/common'; -import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetTypesService } from '../datatarget-types.service'; import { Datatarget } from '../datatarget.model'; -import { DropdownButton } from '@shared/models/dropdown-button.model'; -import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetService } from '../datatarget.service'; +import { DatatargetDetail } from './datatarget-detail'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail-type-selector.directive'; @Component({ selector: 'app-datatarget-detail', @@ -19,79 +14,46 @@ import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; }) export class DatatargetDetailComponent implements OnInit, OnDestroy { - public datatargetSubscription: Subscription; + @ViewChild(DatatargetDetailTypeSelectorDirective, {static: true}) adHost!: DatatargetDetailTypeSelectorDirective; + public datatarget: Datatarget; - public backButton: BackButton = { label: '', routerLink: '/datatarget-list' }; - public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; - private deleteDialogSubscription: Subscription; - public dropdownButton: DropdownButton; - arrowsAltH = faArrowsAltH; - private applicationName: string; - - constructor( - private route: ActivatedRoute, - private deleteDialogService: DeleteDialogService, - private location: Location, - private datatargetRelationServicer: PayloadDeviceDatatargetService, - private datatargetService: DatatargetService, - public translate: TranslateService) { } + private datatargetType: DataTargetType; - ngOnInit(): void { - const id: number = +this.route.snapshot.paramMap.get('datatargetId'); - this.applicationName = this.route.snapshot.paramMap.get('name'); - if (id) { - this.getDatatarget(id); - this.getDatatargetRelations(id); - this.dropdownButton = { - label: '', - editRouterLink: '../../datatarget-edit/' + id, - isErasable: true, - } - } - this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) - .subscribe(translations => { - this.backButton.label = translations['NAV.MY-DATATARGET']; - this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS'] - }); - } + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private datatargetService: DatatargetService, + private route: ActivatedRoute, + private datatargetTypesService: DatatargetTypesService + ) { } - getDatatarget(id: number) { - this.datatargetService.get(id) - .subscribe((dataTarget: Datatarget) => { - this.datatarget = dataTarget; - this.setBackButton(this.datatarget.applicationId); - }); - } - private setBackButton(applicationId: number) { - this.backButton.routerLink = ['applications', applicationId.toString()] - } + loadComponent(componentType: Type) { - onDeleteDatatarget() { - this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( - (response) => { - if (response) { - this.datatargetService.delete(this.datatarget.id).subscribe((response) => { - }); - this.location.back(); - } else { - console.log(response); - } - } - ); - } + const viewContainerRef = this.adHost.viewContainerRef; - getDatatargetRelations(id: number) { - this.datatargetRelationServicer.getByDataTarget(id) - .subscribe((response) => { - this.dataTargetRelations = response.data; - }); + viewContainerRef.clear(); + const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); + viewContainerRef.createComponent(factory); } - ngOnDestroy(): void { - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + ngOnInit(): void { + + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.datatargetType = dataTarget.type; + + const component = this.datatargetTypesService.getDetailComponent(this.datatargetType); + + this.loadComponent(component); + + }); + + } + ngOnDestroy() { + + } } diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts new file mode 100644 index 00000000..4d5f7bfb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts @@ -0,0 +1,2 @@ +export interface DatatargetDetail { +} diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts new file mode 100644 index 00000000..1c28242d --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts @@ -0,0 +1,11 @@ +/* tslint:disable:no-unused-variable */ + +import { ViewContainerRef } from '@angular/core'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit-type-selector.directive'; +let viewContainerRef: ViewContainerRef; +describe('Directive: DatatargetEditTypeSelector', () => { + it('should create an instance', () => { + const directive = new DatatargetEditTypeSelectorDirective(viewContainerRef); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts new file mode 100644 index 00000000..03b06ddb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts @@ -0,0 +1,10 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[edit-component]' +}) +export class DatatargetEditTypeSelectorDirective { + + constructor(public viewContainerRef: ViewContainerRef) { } + +} diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html index 4d4df069..bb9f14e1 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html @@ -1,139 +1,3 @@ - - - -
-
-
    -
  • - {{error | translate}} -
  • -
-
- - {{'DATATARGET.ADD-TO-OPENDATADK' | translate}} - -
-
- * - -
-
- -
-
- * - -
-
-
-
- * - -
-
-
-
- - -
-
- -
-
-
-
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
-
-
- {{'QUESTION.ADD-RELATIONS' | translate}} - - - - - - - - - -
-
- - {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} - - - - - - {{device.name}} - - -
-
-
- - {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} - - - {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} - - - {{payloadDecoder.name}} - - - -
-
- -
- -

{{'DATATARGET.DELETE' | translate}}

-
-
-
-
-
-
- - -
-
\ No newline at end of file +
+ +
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts index fa1f81f9..432a5e2b 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts @@ -1,26 +1,11 @@ -import { Component, OnInit, Input, OnDestroy } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { AfterViewInit, Component, ComponentFactoryResolver, OnDestroy, OnInit, QueryList, Type, ViewChild, ViewChildren } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetTypesService } from '../datatarget-types.service'; import { Datatarget } from '../datatarget.model'; -import { Observable, Subscription } from 'rxjs'; -import { Application } from '@applications/application.model'; -import { IotDevice } from '@applications/iot-devices/iot-device.model'; -import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; -import { PayloadDeviceDatatarget, PayloadDeviceDatatargetGetByDataTargetResponse } from '@payload-decoder/payload-device-data.model'; import { DatatargetService } from '../datatarget.service'; -import { ApplicationService } from '@applications/application.service'; -import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; -import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; -import { SnackService } from '@shared/services/snack.service'; -import { MatDialog } from '@angular/material/dialog'; -import { HttpErrorResponse } from '@angular/common/http'; -import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; -import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; -import { ErrorMessageService } from '@shared/error-message.service'; -import { OpendatadkDialogService } from '@shared/components/opendatadk-dialog/opendatadk-dialog.service'; -import { OpendatadkService } from '@shared/services/opendatadk.service'; -import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; -import { OpenDataDkDataset } from '../opendatadk/opendatadk-dataset.model'; +import { DatatargetEdit } from './datatarget-edit'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit-type-selector.directive'; @Component({ selector: 'app-datatarget-edit', @@ -28,364 +13,57 @@ import { OpenDataDkDataset } from '../opendatadk/opendatadk-dataset.model'; styleUrls: ['./datatarget-edit.component.scss'] }) export class DatatargetEditComponent implements OnInit, OnDestroy { - public multiPage = false; - public title = ''; - public sectionTitle = ''; - public backButtonTitle = ''; - @Input() submitButton: string; - public datatarget: Datatarget = new Datatarget(); - faTimesCircle = faTimesCircle; - public datatargetSubscription: Subscription; - public relationSubscription: Subscription; - public applicationSubscription: Subscription; - public payloadDecoderSubscription: Subscription; - public errorMessages: any; - public errorFields: string[]; - public formFailedSubmit = false; - public datatargetid: number; - private applicationId: number; - private applicationName: string; - public application: Application; - public devices: IotDevice[]; - public payloadDecoders = []; - private counter: number; - private dataSetExcists = false; - private isMailDialogAlreadyShown = false; - payloadDeviceDatatarget: PayloadDeviceDatatarget[]; - newDynamic: any = {}; + @ViewChild(DatatargetEditTypeSelectorDirective, {static: true}) adHost!: DatatargetEditTypeSelectorDirective; - constructor( - public translate: TranslateService, - private route: ActivatedRoute, - private router: Router, - private datatargetService: DatatargetService, - private applicationService: ApplicationService, - private payloadDecoderService: PayloadDecoderService, - private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, - private saveSnackService: SnackService, - private dialog: MatDialog, - private errorMessageService: ErrorMessageService, - private opendatadkService: OpendatadkService, - private opendatadkDialogService: OpendatadkDialogService, - private scrollToTopService: ScrollToTopService, - ) { - translate.use('da'); - } - - ngOnInit() { - this.translate - .get([ - 'FORM.CREATE-NEW-DATATARGET', - 'FORM.EDIT-DATATARGET', - 'DATATARGET.SAVE', - 'NAV.DATATARGET', - ]) - .subscribe((translations) => { - const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); - if (datatargetid !== 0) { - this.title = translations['FORM.EDIT-DATATARGET']; - } else { - this.title = translations['FORM.CREATE-NEW-DATATARGET']; - } - this.submitButton = translations['DATATARGET.SAVE']; - this.backButtonTitle = translations['NAV.DATATARGET']; - }); + public datatarget: Datatarget; + private datatargetType: DataTargetType; - this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); - this.applicationId = +this.route.snapshot.paramMap.get('id'); - this.applicationName = this.route.snapshot.paramMap.get('name'); - if (this.datatargetid !== 0) { - this.getDatatarget(this.datatargetid); - this.getPayloadDeviceDatatarget(this.datatargetid); - } - if (this.applicationId !== 0) { - this.getDevices(); - } - this.getPayloadDecoders(); - this.setDataSetExcists(); - } + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private datatargetService: DatatargetService, + private route: ActivatedRoute, + private datatargetTypesService: DatatargetTypesService + ) { } - addRow() { - if (!this.payloadDeviceDatatarget) { - this.payloadDeviceDatatarget = []; - } - this.payloadDeviceDatatarget.push({ id: null, iotDeviceIds: [], payloadDecoderId: null, dataTargetId: this.datatargetid }); - } - private deleteRow(index) { - if (this.payloadDeviceDatatarget.length === 0) { - } else if (this.payloadDeviceDatatarget[index]?.id === null) { - this.payloadDeviceDatatarget.splice(index, 1); - } else { - this.payloadDeviceDataTargetService.delete(this.payloadDeviceDatatarget[index].id) - .subscribe((response) => { - this.payloadDeviceDatatarget.splice(index, 1); - }); - } + loadComponent(componentType: Type) { + const viewContainerRef = this.adHost.viewContainerRef; + viewContainerRef.clear(); + const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); + viewContainerRef.createComponent(factory); } - openDeleteDialog(index) { - const dialog = this.dialog.open(DeleteDialogComponent, { - data: { - showAccept: true, - showCancel: true, - message: 'Er du sikker på at du vil slette?' - } - }); - - dialog.afterClosed().subscribe((result) => { - if (result === true) { - this.deleteRow(index); - } - }); - } - - onSubmit(): void { - this.counter = 0; - if (this.datatargetid) { - this.updateDatatarget(); - this.addPayloadDeviceDatatarget(); - } else { - this.createDatatarget(); - } - } - - public compare(o1: any, o2: any): boolean { - return o1 === o2; - } + ngOnInit(): void { - updateDatatarget() { - this.resetErrors(); - this.counter = 1 + (this.payloadDeviceDatatarget?.length ? this.payloadDeviceDatatarget?.length : 0); - this.datatargetService.update(this.datatarget) - .subscribe( - (response: Datatarget) => { - this.datatarget = response; - if (this.datatarget.openDataDkDataset != null) { - this.datatarget.openDataDkDataset.acceptTerms = true; - } - this.shouldShowMailDialog().subscribe( - (response) => { - this.countToRedirect(); - } - ); - }, - (error: HttpErrorResponse) => { - this.checkDataTargetModelOpendatadkdatasaet(); - this.handleError(error); - this.formFailedSubmit = true; - } - ); - } + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); - addPayloadDeviceDatatarget() { - this.payloadDeviceDatatarget.map( - pdd => { - if (pdd.payloadDecoderId === 0) { - pdd.payloadDecoderId = null; - } - } - ); - this.payloadDeviceDatatarget.forEach((relation) => { - if (relation.id) { - this.payloadDeviceDataTargetService.put(relation).subscribe( - (response) => { - this.countToRedirect(); - }, - (error) => { - this.handleError(error); - } - ); + if (id > 0) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.datatargetType = dataTarget.type; + const component = this.datatargetTypesService.getEditComponent(this.datatargetType); + this.loadComponent(component); + }); } else { - this.payloadDeviceDataTargetService.post(relation).subscribe( - (res: any) => { - this.countToRedirect(); - }, - (error) => { - this.handleError(error); - } - ); - } - }); - } - - countToRedirect() { - this.counter -= 1; - if (this.counter <= 0 && !this.formFailedSubmit) { - this.showSavedSnack(); - this.routeToDatatargets(); - } - } - - getPayloadDeviceDatatarget(id: number) { - this.relationSubscription = this.payloadDeviceDataTargetService - .getByDataTarget(id) - .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { - this.mapToDatatargetDevicePayload(response); - }); - } - - createDatatarget() { - this.resetErrors(); - this.datatarget.applicationId = this.applicationId; - this.datatargetService.create(this.datatarget) - .subscribe((response: Datatarget) => { - this.datatargetid = response.id; - this.datatarget = response; - if (this.datatarget.openDataDkDataset != null) { - this.datatarget.openDataDkDataset.acceptTerms = true; + let datatargetTypeParam = this.route.snapshot.paramMap.get('datatargetType'); + this.datatargetType = this.enumFromStringValue(DataTargetType, datatargetTypeParam); + if (this.datatargetType) { + const component = this.datatargetTypesService.getEditComponent(this.datatargetType); + this.loadComponent(component); } - this.showSavedSnack(); - this.routeToDatatargets(); - }, - (error: HttpErrorResponse) => { - this.checkDataTargetModelOpendatadkdatasaet(); - this.handleError(error); - this.formFailedSubmit = true; - }); - - } - - private resetErrors() { - this.errorFields = []; - this.errorMessages = undefined; - this.formFailedSubmit = false; - } - - checkDataTargetModelOpendatadkdatasaet() { - if (!this.datatarget.openDataDkDataset) { - this.datatarget.openDataDkDataset = new OpenDataDkDataset(); - } - } - - getDevices(): void { - this.applicationSubscription = this.applicationService.getApplication(this.applicationId) - .subscribe((application: Application) => { - this.devices = application.iotDevices; - }); - } - - public selectAllDevices(index: number) { - this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map(device => device.id); - } - - public deSelectAllDevices(index: number) { - this.payloadDeviceDatatarget[index].iotDeviceIds = []; - } - - getPayloadDecoders() { - this.payloadDecoderSubscription = this.payloadDecoderService.getMultiple(1000, 0, 'id', 'ASC') - .subscribe((response: PayloadDecoderMappedResponse) => { - this.payloadDecoders = response.data; - }); - } - - handleError(error: HttpErrorResponse) { - const errors = this.errorMessageService.handleErrorMessageWithFields(error); - this.errorFields = errors.errorFields; - this.errorMessages = errors.errorMessages; - this.scrollToTopService.scrollToTop(); - } - - routeToDatatargets(): void { - this.router.navigate(['applications',this.applicationId.toString()]) - } - - onCoordinateKey(event: any) { - if (event.target.value.length > event.target.maxLength) { - event.target.value = event.target.value.slice( - 0, - event.target.maxLength - ); - } - } - - getDatatarget(id: number) { - this.datatargetSubscription = this.datatargetService - .get(id) - .subscribe((response: Datatarget) => { - this.datatarget = response; - }); - } - - showSavedSnack() { - this.saveSnackService.showSavedSnack(); - } - - private setDataSetExcists() { - this.opendatadkService.get().subscribe( - (response) => { - this.dataSetExcists = response.dataset.length === 0 ? false : true; } - ); - } - private shouldShowMailDialog(): Observable { - return new Observable( - (observer) => { - if (!this.dataSetExcists && this.datatarget.setToOpendataDk && !this.isMailDialogAlreadyShown) { - this.isMailDialogAlreadyShown = true; - this.opendatadkDialogService.showDialog().subscribe( - response => { - if (response) { - this.showMailClient(); - } - observer.next(response); - } - ); - } else { - observer.next(true); - } - } - ) - } - private showMailClient() { - if (!this.datatarget.openDataDkDataset.url) { - this.datatarget.openDataDkDataset.url = this.datatargetService.getOpendataSharingApiUrl() - } - window.location.href = 'mailto:FG2V@kk.dk?subject=Oprettelse%20af%20datas%C3%A6t%20i%20OpenDataDK&body=K%C3%A6re%20Frans%0D%0A%0D%0AHermed%20fremsendes%20linket%20til%20DCAT%20kataloget%20%2C%20du%20bedes%20registrere%20p%C3%A5%20Open%20Data%20DK%20platformen.%0D%0A%0D%0ALink%3A ' + this.datatarget.openDataDkDataset.url; } - disableSaveButton(): boolean { - let disable = true; - if (!this.datatarget.setToOpendataDk) { - disable = false; - } else if (this.datatarget.openDataDkDataset?.acceptTerms) { - disable = false; - } else { - disable = true; - } - return disable; + enumFromStringValue(enm: { [s: string]: T}, value: string): T | undefined { + return (Object.values(enm) as unknown as string[]).includes(value) + ? value as unknown as T + : undefined; } - ngOnDestroy(): void { - if (this.relationSubscription) { - this.relationSubscription.unsubscribe(); - } - if (this.applicationSubscription) { - this.applicationSubscription.unsubscribe(); - } - if (this.datatargetSubscription) { - this.datatargetSubscription.unsubscribe(); - } - if (this.payloadDecoderSubscription) { - this.payloadDecoderSubscription.unsubscribe(); - } - } + ngOnDestroy() { - private mapToDatatargetDevicePayload(dto: PayloadDeviceDatatargetGetByDataTargetResponse) { - this.payloadDeviceDatatarget = []; - dto.data.forEach( - (element) => { - this.payloadDeviceDatatarget.push({ - id: element.id, - iotDeviceIds: element.iotDevices.map((x) => x.id), - payloadDecoderId: element.payloadDecoder?.id === undefined ? 0 : element.payloadDecoder?.id, - dataTargetId: element.dataTarget.id - }); - } - ); - } + } } diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts new file mode 100644 index 00000000..16def301 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts @@ -0,0 +1,2 @@ +export interface DatatargetEdit { +} diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html new file mode 100644 index 00000000..6d654898 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html @@ -0,0 +1,34 @@ + + + +
+
+ + +
+
+ + + +
+ {{dataTargetType.name}} +
+ + {{dataTargetType.provider}} + +
+ + {{ dataTargetType.description }} + + + + + + + + +
+
+
diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss new file mode 100644 index 00000000..71485bcb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss @@ -0,0 +1,44 @@ +.data-component { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-left: 10px; +} + +.mat-card{ + display:flex; + flex-direction: column; + flex-wrap: wrap; + width: 300px; + min-width: 300px; + max-width: 400px; + height: 320px; + margin: 10px; +} + +.mat-card-header { + flex-shrink: 1; + +} + +.mat-card-content{ + flex-grow: 1; + overflow: auto; + margin-left: 10px; +} + + +mat-card img{ + object-fit: contain; /*this makes the image in src fit to the size specified below*/ + object-position: left; + width: 50%; /* Here you can use wherever you want to specify the width and also the height of the */ + height: 20px; + margin-left: 3px; + margin-top: 3px; + margin-bottom: 3px; +} + +.img-placeholder{ + height: 26px; + clear: both; +} diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts new file mode 100644 index 00000000..4f1f6508 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { DatatargetNewComponent } from './datatarget-new.component'; + +describe('DatatargetNewComponent', () => { + let component: DatatargetNewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DatatargetNewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DatatargetNewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts new file mode 100644 index 00000000..d3f90b12 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { DatatargetTypeDescriptor } from '../datatarget.model'; +import { DatatargetTypesService } from '../datatarget-types.service'; + + + +@Component({ + selector: 'app-datatarget-new', + templateUrl: './datatarget-new.component.html', + styleUrls: ['./datatarget-new.component.scss'] +}) +export class DatatargetNewComponent implements OnInit { + + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + public submitButton = ''; + public avaiableDataTargetTypes : DatatargetTypeDescriptor[]; + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private dataTargetTypesService: DatatargetTypesService + ) { + translate.use('da'); + } + + ngOnInit() { + + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.avaiableDataTargetTypes = this.dataTargetTypesService.getAvailableDataTargetTypes(); + + } + + public createNewOf(typeDescriptor: DatatargetTypeDescriptor) + { + this.router.navigate(['../datatarget-edit', {datatargetType: typeDescriptor.type}], {relativeTo:this. route}); + } + public showReadMe(typeDescriptor: DatatargetTypeDescriptor) + { + window.open(typeDescriptor.readMoreUrl, "_blank"); + } +} diff --git a/src/app/applications/datatarget/datatarget-response.model.ts b/src/app/applications/datatarget/datatarget-response.model.ts index 1d6b809d..fcb40286 100644 --- a/src/app/applications/datatarget/datatarget-response.model.ts +++ b/src/app/applications/datatarget/datatarget-response.model.ts @@ -9,6 +9,8 @@ export class DatatargetResponse { timeout: number; type: DataTargetType; url: string; + tenant: string; + context: string; authorizationHeader: string; openDataDkDataset: OpenDataDkDataset; createdAt: string; diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html index c839d784..d2b4aca8 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html @@ -21,7 +21,7 @@ {{ 'DATATARGET-TABLE.TYPE' | translate }} - {{element.type | translate}} + {{'DATATARGET.' + element.type | translate}} diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts index fa4eb35d..65c04906 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts @@ -46,8 +46,7 @@ export class DatatargetTableComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit(): void { - this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); - console.log(this.applicationId); + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); this.getDatatarget(); this.canEdit = this.meService.canWriteInTargetOrganization() } diff --git a/src/app/applications/datatarget/datatarget-types.service.spec.ts b/src/app/applications/datatarget/datatarget-types.service.spec.ts new file mode 100644 index 00000000..d5ef25d5 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-types.service.spec.ts @@ -0,0 +1,16 @@ +/* tslint:disable:no-unused-variable */ + +import { TestBed, async, inject } from '@angular/core/testing'; +import { DatatargetTypesService } from './datatarget-types.service'; + +describe('Service: DatatargetTypesService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [DatatargetTypesService] + }); + }); + + it('should ...', inject([DatatargetTypesService], (service: DatatargetTypesService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/applications/datatarget/datatarget-types.service.ts b/src/app/applications/datatarget/datatarget-types.service.ts new file mode 100644 index 00000000..581788cf --- /dev/null +++ b/src/app/applications/datatarget/datatarget-types.service.ts @@ -0,0 +1,84 @@ +import { Injectable, Type } from '@angular/core'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetDetail } from './datatarget-detail/datatarget-detail'; +import { DatatargetEdit } from './datatarget-edit/datatarget-edit'; +import { DatatargetTypeDescriptor } from './datatarget.model'; +import { FiwareDetailComponent } from './fiware/fiware-detail/fiware-detail.component'; +import { FiwareEditComponent } from './fiware/fiware-edit/fiware-edit.component'; +import { HttppushDetailComponent } from './httppush/httppush-detail/httppush-detail.component'; +import { HttppushEditComponent } from './httppush/httppush-edit/httppush-edit.component'; + +@Injectable({ + providedIn: 'root' +}) +export class DatatargetTypesService { + +constructor() { } + + getAvailableDataTargetTypes() : DatatargetTypeDescriptor[] + { + return [ + { name: 'Generisk HTTP Push', + type: DataTargetType.HTTPPUSH, + icon: null, + description: 'Send data med HTTP POST requests til et HTTP URL endpoint', + readMoreUrl: '', + provider: 'OS2' + + }, + { name: 'Open Data DK', + type: DataTargetType.OPENDATADK, + icon: '/assets/images/logo_opendatadk.svg', + description: 'Offentliggør datasæt i Open Data DK\'s åbne dataportal.', + readMoreUrl: 'https://www.opendata.dk/', + provider: 'OS2' + + }, + { name: 'FIWARE connection', + type: DataTargetType.FIWARE, + icon: '/assets/images/logo_FIWARE.png', + description: 'En integration til FIWARE Context Broker' , + readMoreUrl: 'https://www.kmd.dk', + provider: 'KMD A/S' + } + ] + } + + getDetailComponent(dataTargetType: DataTargetType): Type + { + if (dataTargetType === DataTargetType.HTTPPUSH) + { + return HttppushDetailComponent; + } + + if (dataTargetType === DataTargetType.OPENDATADK) + { + return HttppushDetailComponent; + } + + if (dataTargetType === DataTargetType.FIWARE) + { + return FiwareDetailComponent; + } + } + + getEditComponent(dataTargetType: DataTargetType): Type + { + if (dataTargetType === DataTargetType.HTTPPUSH) + { + return HttppushEditComponent; + } + + if (dataTargetType === DataTargetType.OPENDATADK) + { + return HttppushEditComponent; + } + + if (dataTargetType === DataTargetType.FIWARE) + { + return FiwareEditComponent; + } + } + +} + diff --git a/src/app/applications/datatarget/datatarget.model.ts b/src/app/applications/datatarget/datatarget.model.ts index 03fba64b..12e846c9 100644 --- a/src/app/applications/datatarget/datatarget.model.ts +++ b/src/app/applications/datatarget/datatarget.model.ts @@ -7,6 +7,8 @@ export class Datatarget { applicationId: number; type: DataTargetType = DataTargetType.HTTPPUSH; url: string; + tenant: string; + context: string; //default 30 sec timeout: number = 30000; authorizationHeader: string; @@ -24,4 +26,14 @@ export class DatatargetData { data: Datatarget[]; ok?: boolean; count?: number; +} + +export class DatatargetTypeDescriptor +{ + name: string; + type: DataTargetType; + icon: string; + description: string; + readMoreUrl: string; + provider: string; } \ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget.module.ts b/src/app/applications/datatarget/datatarget.module.ts index 46be0a33..ac65cbea 100644 --- a/src/app/applications/datatarget/datatarget.module.ts +++ b/src/app/applications/datatarget/datatarget.module.ts @@ -14,15 +14,29 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { SharedModule } from '@shared/shared.module'; import { PipesModule } from '@shared/pipes/pipes.module'; +import { DatatargetNewComponent } from './datatarget-new/datatarget-new.component'; +import { FiwareEditComponent } from './fiware/fiware-edit/fiware-edit.component'; +import { FiwareDetailComponent } from './fiware/fiware-detail/fiware-detail.component'; +import { HttppushDetailComponent } from './httppush/httppush-detail/httppush-detail.component'; +import { HttppushEditComponent } from './httppush/httppush-edit/httppush-edit.component'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail/datatarget-detail-type-selector.directive'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit/datatarget-edit-type-selector.directive'; @NgModule({ - declarations: [ + declarations: [ DatatargetTableComponent, DatatargetEditComponent, + DatatargetNewComponent, DatatargetDetailComponent, + FiwareDetailComponent, + FiwareEditComponent, + HttppushDetailComponent, + HttppushEditComponent, OpendatadkComponent, OpendatadkEditComponent, - OpendatadkDetailComponent], + OpendatadkDetailComponent, + DatatargetDetailTypeSelectorDirective, + DatatargetEditTypeSelectorDirective], imports: [ CommonModule, RouterModule, @@ -38,7 +52,12 @@ import { PipesModule } from '@shared/pipes/pipes.module'; exports: [ DatatargetTableComponent, DatatargetEditComponent, - DatatargetDetailComponent, + DatatargetNewComponent, + DatatargetDetailComponent, + FiwareDetailComponent, + FiwareEditComponent, + HttppushDetailComponent, + HttppushEditComponent, NGMaterialModule ] }) diff --git a/src/app/applications/datatarget/datatarget.service.ts b/src/app/applications/datatarget/datatarget.service.ts index 74e3688c..6647ed30 100644 --- a/src/app/applications/datatarget/datatarget.service.ts +++ b/src/app/applications/datatarget/datatarget.service.ts @@ -90,6 +90,8 @@ export class DatatargetService { timeout: dataTargetResponse.timeout, type: dataTargetResponse.type, url: dataTargetResponse.url, + tenant: dataTargetResponse.tenant, + context: dataTargetResponse.context, authorizationHeader: dataTargetResponse.authorizationHeader, applicationId: dataTargetResponse.application.id, setToOpendataDk: dataTargetResponse?.openDataDkDataset ? true : false, diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html new file mode 100644 index 00000000..8e895d9c --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html @@ -0,0 +1,76 @@ +
+ +
+
+
+
+

{{ 'DATATARGET.DETAILS' | translate }}

+ + +

Context Broker{{datatarget.url}}

+

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

+

{{ 'DATATARGET.TYPE' | translate }}{{'DATATARGET.' + datatarget.type | translate}}

+ + +

{{ 'DATATARGET.TENANT' | translate }}

+
{{datatarget.tenant}}
+ +

{{ 'DATATARGET.NO-TENANT' | translate }}

+
+ +

{{ 'DATATARGET.CONTEXT' | translate }}

+
{{datatarget.context}}
+ +

{{ 'DATATARGET.NO-CONTEXT' | translate }}

+
+ +

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

+
{{datatarget.authorizationHeader}}
+ +

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

+
+ +
+
+ + +
+
+
+
+

{{ 'DATATARGET.RELATIONS' | translate }}

+
+

{{'DATATARGET.NO-RELATIONS' | translate}}

+
+
+
+
+
+

{{'DATATARGET.PAYLOADEDECODER' | translate}} + {{relation.payloadDecoder.name}} + + {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} +

+
+
+ +
+
+

{{'DATATARGET.IOTDEVICE' | translate}} + + , {{device.name}} + +

+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.scss b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts new file mode 100644 index 00000000..7f5198d9 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { FiwareDetailComponent } from './fiware-detail.component'; + +describe('FiwareDetailComponent', () => { + let component: FiwareDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiwareDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiwareDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts new file mode 100644 index 00000000..d56148e6 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts @@ -0,0 +1,98 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; +import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DatatargetService } from '../../datatarget.service'; +import { Location } from '@angular/common'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { Datatarget } from '../../datatarget.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetDetail } from '@applications/datatarget/datatarget-detail/datatarget-detail'; + + +@Component({ + selector: 'app-fiware-detail', + templateUrl: './fiware-detail.component.html', + styleUrls: ['./fiware-detail.component.scss'] +}) +export class FiwareDetailComponent implements DatatargetDetail, OnInit, OnDestroy { + + public datatargetSubscription: Subscription; + public datatarget: Datatarget; + public backButton: BackButton = { label: '', routerLink: '' }; + public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + arrowsAltH = faArrowsAltH; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private location: Location, + private datatargetRelationServicer: PayloadDeviceDatatargetService, + private datatargetService: DatatargetService, + public translate: TranslateService) { } + + ngOnInit(): void { + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + + if (id) { + this.getDatatarget(id); + this.getDatatargetRelations(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../datatarget-edit/' + id, + isErasable: true, + }; + } + this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) + .subscribe(translations => { + this.backButton.label = translations['NAV.MY-DATATARGET']; + this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS']; + }); + } + + getDatatarget(id: number) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.setBackButton(this.datatarget.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = ['applications', applicationId.toString() ]; + } + + onDeleteDatatarget() { + this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( + (response) => { + if (response) { + this.datatargetService.delete(this.datatarget.id).subscribe((response) => { + }); + this.location.back(); + } else { + console.log(response); + } + } + ); + } + + getDatatargetRelations(id: number) { + this.datatargetRelationServicer.getByDataTarget(id) + .subscribe((response) => { + this.dataTargetRelations = response.data; + }); + } + + ngOnDestroy(): void { + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html new file mode 100644 index 00000000..02ec7529 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html @@ -0,0 +1,167 @@ + + + +
+ +
+
    +
  • + {{error | translate}} +
  • +
+
+ +
+
+ * + +
+
+ +
+
+ * + +
+
+ +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+ * + +
+
+ +
+
+ + +
+
+
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
+
+
+ {{'QUESTION.ADD-RELATIONS' | translate}} + + + + + + + + + +
+
+ + {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} + + + + + + {{device.name}} + + +
+
+
+ + {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} + + + {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} + + + {{payloadDecoder.name}} + + + +
+
+ +
+ +

{{'DATATARGET.DELETE' | translate}}

+
+
+
+
+
+ +
+ + +
+
+
diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss new file mode 100644 index 00000000..bb96f657 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss @@ -0,0 +1,4 @@ +.form-info-icon { + margin-left: 5px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts new file mode 100644 index 00000000..6a58e7f6 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { FiwareEditComponent } from './fiware-edit.component'; + +describe('FiwareEditComponent', () => { + let component: FiwareEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiwareEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiwareEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts new file mode 100644 index 00000000..b52a823b --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts @@ -0,0 +1,336 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Datatarget } from '../../datatarget.model'; +import { Observable, Subscription } from 'rxjs'; +import { Application } from '@applications/application.model'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; +import { PayloadDeviceDatatarget, PayloadDeviceDatatargetGetByDataTargetResponse } from '@payload-decoder/payload-device-data.model'; +import { DatatargetService } from '../../datatarget.service'; +import { ApplicationService } from '@applications/application.service'; +import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; +import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; +import { SnackService } from '@shared/services/snack.service'; +import { MatDialog } from '@angular/material/dialog'; +import { HttpErrorResponse } from '@angular/common/http'; +import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; +import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetEdit } from '@applications/datatarget/datatarget-edit/datatarget-edit'; + +@Component({ + selector: 'app-fiware-edit', + templateUrl: './fiware-edit.component.html', + styleUrls: ['./fiware-edit.component.scss'] +}) +export class FiwareEditComponent implements DatatargetEdit, OnInit, OnDestroy { + + + public multiPage = false; + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + @Input() submitButton: string; + public datatarget: Datatarget = new Datatarget(); + faTimesCircle = faTimesCircle; + public datatargetSubscription: Subscription; + public relationSubscription: Subscription; + public applicationSubscription: Subscription; + public payloadDecoderSubscription: Subscription; + public errorMessages: any; + public errorFields: string[]; + public formFailedSubmit = false; + public datatargetid: number; + private applicationId: number; + public application: Application; + public devices: IotDevice[]; + public payloadDecoders = []; + private counter: number; + payloadDeviceDatatarget: PayloadDeviceDatatarget[]; + newDynamic: any = {}; + faQuestionCircle = faQuestionCircle; + + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private datatargetService: DatatargetService, + private applicationService: ApplicationService, + private payloadDecoderService: PayloadDecoderService, + private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, + private snackService: SnackService, + private dialog: MatDialog, + private errorMessageService: ErrorMessageService, + private scrollToTopService: ScrollToTopService, + ) { + translate.use('da'); + } + + + + ngOnInit() { + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + + this.datatarget.type = DataTargetType.FIWARE; + + if (this.datatargetid !== 0) { + this.getDatatarget(this.datatargetid); + this.getPayloadDeviceDatatarget(this.datatargetid); + } + if (this.applicationId !== 0) { + this.getDevices(); + } + this.getPayloadDecoders(); + + } + + + addRow() { + if (!this.payloadDeviceDatatarget) { + this.payloadDeviceDatatarget = []; + } + this.payloadDeviceDatatarget.push({ id: null, iotDeviceIds: [], payloadDecoderId: null, dataTargetId: this.datatargetid }); + } + + private deleteRow(index) { + if (this.payloadDeviceDatatarget.length === 0) { + } else if (this.payloadDeviceDatatarget[index]?.id === null) { + this.payloadDeviceDatatarget.splice(index, 1); + } else { + this.payloadDeviceDataTargetService.delete(this.payloadDeviceDatatarget[index].id) + .subscribe((response) => { + this.payloadDeviceDatatarget.splice(index, 1); + }); + } + } + + openDeleteDialog(index) { + const dialog = this.dialog.open(DeleteDialogComponent, { + data: { + showAccept: true, + showCancel: true, + message: 'Er du sikker på at du vil slette?' + } + }); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.deleteRow(index); + } + }); + } + + onSubmit(): void { + this.counter = 0; + if (this.datatargetid) { + this.updateDatatarget(); + this.addPayloadDeviceDatatarget(); + } else { + this.createDatatarget(); + } + } + + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + updateDatatarget() { + this.resetErrors(); + this.counter = 1 + (this.payloadDeviceDatatarget?.length ? this.payloadDeviceDatatarget?.length : 0); + this.datatargetService.update(this.datatarget) + .subscribe( + (response: Datatarget) => { + this.datatarget = response; + this.countToRedirect(); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + addPayloadDeviceDatatarget() { + this.payloadDeviceDatatarget.map( + pdd => { + if (pdd.payloadDecoderId === 0) { + pdd.payloadDecoderId = null; + } + } + ); + this.payloadDeviceDatatarget.forEach((relation) => { + if (relation.id) { + this.payloadDeviceDataTargetService.put(relation).subscribe( + (response) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } else { + this.payloadDeviceDataTargetService.post(relation).subscribe( + (res: any) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } + }); + } + + countToRedirect() { + this.counter -= 1; + if (this.counter <= 0 && !this.formFailedSubmit) { + this.showSavedSnack(); + this.routeToDatatargets(); + } + } + + getPayloadDeviceDatatarget(id: number) { + this.relationSubscription = this.payloadDeviceDataTargetService + .getByDataTarget(id) + .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { + this.mapToDatatargetDevicePayload(response); + }); + } + + createDatatarget() { + this.resetErrors(); + this.datatarget.applicationId = this.applicationId; + this.datatargetService.create(this.datatarget) + .subscribe((response: Datatarget) => { + this.datatargetid = response.id; + this.datatarget = response; + this.showSavedSnack(); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + }); + + } + + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + + + + getDevices(): void { + this.applicationSubscription = this.applicationService.getApplication(this.applicationId) + .subscribe((application: Application) => { + this.devices = application.iotDevices; + }); + } + + public selectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map(device => device.id); + } + + public deSelectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = []; + } + + getPayloadDecoders() { + this.payloadDecoderSubscription = this.payloadDecoderService.getMultiple(1000, 0, 'id', 'ASC') + .subscribe((response: PayloadDecoderMappedResponse) => { + this.payloadDecoders = response.data; + }); + } + + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + + routeToDatatargets(): void { + this.router.navigate(['applications', this.applicationId.toString()]); + } + + onCoordinateKey(event: any) { + if (event.target.value.length > event.target.maxLength) { + event.target.value = event.target.value.slice( + 0, + event.target.maxLength + ); + } + } + + getDatatarget(id: number) { + this.datatargetSubscription = this.datatargetService + .get(id) + .subscribe((response: Datatarget) => { + this.datatarget = response; + }); + } + + showSavedSnack() { + this.snackService.showSavedSnack(); + } + + disableSaveButton(): boolean { + const disable = false; + + return disable; + } + + ngOnDestroy(): void { + if (this.relationSubscription) { + this.relationSubscription.unsubscribe(); + } + if (this.applicationSubscription) { + this.applicationSubscription.unsubscribe(); + } + if (this.datatargetSubscription) { + this.datatargetSubscription.unsubscribe(); + } + if (this.payloadDecoderSubscription) { + this.payloadDecoderSubscription.unsubscribe(); + } + } + + private mapToDatatargetDevicePayload(dto: PayloadDeviceDatatargetGetByDataTargetResponse) { + this.payloadDeviceDatatarget = []; + dto.data.forEach( + (element) => { + this.payloadDeviceDatatarget.push({ + id: element.id, + iotDeviceIds: element.iotDevices.map((x) => x.id), + payloadDecoderId: element.payloadDecoder?.id === undefined ? 0 : element.payloadDecoder?.id, + dataTargetId: element.dataTarget.id + }); + } + ); + } +} diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html new file mode 100644 index 00000000..42cf6d1f --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html @@ -0,0 +1,74 @@ +
+ +
+
+
+
+

{{ 'DATATARGET.DETAILS' | translate }}

+ + +

{{ 'DATATARGET.URL' | translate }}{{datatarget.url}}

+

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

+

{{ 'DATATARGET.TYPE' | translate }}{{'DATATARGET.' + datatarget.type | translate}}

+ +

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

+
{{datatarget.authorizationHeader}}
+ +

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

+
+ +
+
+
+
+

{{ 'DATATARGET.OPENDATA-DK' | translate }}

+
+ +
+ +

{{ 'DATATARGET.NO-OPENDATA-DK' | translate }}

+
+
+
+
+ +
+
+
+
+

{{ 'DATATARGET.RELATIONS' | translate }}

+
+

{{'DATATARGET.NO-RELATIONS' | translate}}

+
+
+
+
+
+

{{'DATATARGET.PAYLOADEDECODER' | translate}} + {{relation.payloadDecoder.name}} + + {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} +

+
+
+ +
+
+

{{'DATATARGET.IOTDEVICE' | translate}} + + , {{device.name}} + +

+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss new file mode 100644 index 00000000..e5e49dff --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss @@ -0,0 +1,3 @@ +pre { + word-wrap: break-word; + } \ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts new file mode 100644 index 00000000..a212025a --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { HttppushDetailComponent } from './httppush-detail.component'; + +describe('HttppushDetailComponent', () => { + let component: HttppushDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HttppushDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HttppushDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts new file mode 100644 index 00000000..179aebcf --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts @@ -0,0 +1,98 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; +import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DatatargetService } from '../../datatarget.service'; +import { Location } from '@angular/common'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { Datatarget } from '../../datatarget.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetDetail } from '@applications/datatarget/datatarget-detail/datatarget-detail'; + +@Component({ + selector: 'app-httppush-detail', + templateUrl: './httppush-detail.component.html', + styleUrls: ['./httppush-detail.component.scss'] +}) +export class HttppushDetailComponent implements DatatargetDetail, OnInit, OnDestroy { + + public datatargetSubscription: Subscription; + public datatarget: Datatarget; + public backButton: BackButton = { label: '', routerLink: '' }; + public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + arrowsAltH = faArrowsAltH; + private applicationName: string; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private location: Location, + private datatargetRelationServicer: PayloadDeviceDatatargetService, + private datatargetService: DatatargetService, + public translate: TranslateService) { } + + ngOnInit(): void { + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + if (id) { + this.getDatatarget(id); + this.getDatatargetRelations(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../datatarget-edit/' + id, + isErasable: true, + }; + } + this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) + .subscribe(translations => { + this.backButton.label = translations['NAV.MY-DATATARGET']; + this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS']; + }); + } + + getDatatarget(id: number) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.setBackButton(this.datatarget.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = ['applications', applicationId.toString()]; + } + + onDeleteDatatarget() { + this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( + (response) => { + if (response) { + this.datatargetService.delete(this.datatarget.id).subscribe((response) => { + }); + this.location.back(); + } else { + console.log(response); + } + } + ); + } + + getDatatargetRelations(id: number) { + this.datatargetRelationServicer.getByDataTarget(id) + .subscribe((response) => { + this.dataTargetRelations = response.data; + }); + } + + ngOnDestroy(): void { + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html new file mode 100644 index 00000000..4d4df069 --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html @@ -0,0 +1,139 @@ + + + +
+
+
    +
  • + {{error | translate}} +
  • +
+
+ + {{'DATATARGET.ADD-TO-OPENDATADK' | translate}} + +
+
+ * + +
+
+ +
+
+ * + +
+
+
+
+ * + +
+
+
+
+ + +
+
+ +
+
+
+
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
+
+
+ {{'QUESTION.ADD-RELATIONS' | translate}} + + + + + + + + + +
+
+ + {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} + + + + + + {{device.name}} + + +
+
+
+ + {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} + + + {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} + + + {{payloadDecoder.name}} + + + +
+
+ +
+ +

{{'DATATARGET.DELETE' | translate}}

+
+
+
+
+
+
+ + +
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.scss b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts new file mode 100644 index 00000000..1ff864db --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { HttppushEditComponent } from './httppush-edit.component'; + +describe('HttppushEditComponent', () => { + let component: HttppushEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HttppushEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HttppushEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts new file mode 100644 index 00000000..d192c2a6 --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts @@ -0,0 +1,406 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Datatarget } from '../../datatarget.model'; +import { Observable, Subscription } from 'rxjs'; +import { Application } from '@applications/application.model'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; +import { + PayloadDeviceDatatarget, + PayloadDeviceDatatargetGetByDataTargetResponse, +} from '@payload-decoder/payload-device-data.model'; +import { DatatargetService } from '../../datatarget.service'; +import { ApplicationService } from '@applications/application.service'; +import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; +import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; +import { SnackService } from '@shared/services/snack.service'; +import { MatDialog } from '@angular/material/dialog'; +import { HttpErrorResponse } from '@angular/common/http'; +import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; +import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { OpendatadkDialogService } from '@shared/components/opendatadk-dialog/opendatadk-dialog.service'; +import { OpendatadkService } from '@shared/services/opendatadk.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { OpenDataDkDataset } from '../../opendatadk/opendatadk-dataset.model'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetEdit } from '@applications/datatarget/datatarget-edit/datatarget-edit'; + +@Component({ + selector: 'app-httppush-edit', + templateUrl: './httppush-edit.component.html', + styleUrls: ['./httppush-edit.component.scss'], +}) +export class HttppushEditComponent + implements DatatargetEdit, OnInit, OnDestroy { + public multiPage = false; + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + @Input() submitButton: string; + public datatarget: Datatarget = new Datatarget(); + faTimesCircle = faTimesCircle; + public datatargetSubscription: Subscription; + public relationSubscription: Subscription; + public applicationSubscription: Subscription; + public payloadDecoderSubscription: Subscription; + public errorMessages: any; + public errorFields: string[]; + public formFailedSubmit = false; + public datatargetid: number; + private applicationId: number; + private applicationName: string; + public application: Application; + public devices: IotDevice[]; + public payloadDecoders = []; + private counter: number; + private dataSetExcists = false; + private isMailDialogAlreadyShown = false; + + payloadDeviceDatatarget: PayloadDeviceDatatarget[]; + newDynamic: any = {}; + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private datatargetService: DatatargetService, + private applicationService: ApplicationService, + private payloadDecoderService: PayloadDecoderService, + private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, + private saveSnackService: SnackService, + private dialog: MatDialog, + private errorMessageService: ErrorMessageService, + private opendatadkService: OpendatadkService, + private opendatadkDialogService: OpendatadkDialogService, + private scrollToTopService: ScrollToTopService + ) { + translate.use('da'); + } + + ngOnInit() { + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + if (this.datatargetid !== 0) { + this.getDatatarget(this.datatargetid); + this.getPayloadDeviceDatatarget(this.datatargetid); + } + if (this.applicationId !== 0) { + this.getDevices(); + } + this.getPayloadDecoders(); + this.setDataSetExcists(); + } + + addRow() { + if (!this.payloadDeviceDatatarget) { + this.payloadDeviceDatatarget = []; + } + this.payloadDeviceDatatarget.push({ + id: null, + iotDeviceIds: [], + payloadDecoderId: null, + dataTargetId: this.datatargetid, + }); + } + + private deleteRow(index) { + if (this.payloadDeviceDatatarget.length === 0) { + } else if (this.payloadDeviceDatatarget[index]?.id === null) { + this.payloadDeviceDatatarget.splice(index, 1); + } else { + this.payloadDeviceDataTargetService + .delete(this.payloadDeviceDatatarget[index].id) + .subscribe((response) => { + this.payloadDeviceDatatarget.splice(index, 1); + }); + } + } + + openDeleteDialog(index) { + const dialog = this.dialog.open(DeleteDialogComponent, { + data: { + showAccept: true, + showCancel: true, + message: 'Er du sikker på at du vil slette?', + }, + }); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.deleteRow(index); + } + }); + } + + onSubmit(): void { + this.counter = 0; + if (this.datatargetid) { + this.updateDatatarget(); + this.addPayloadDeviceDatatarget(); + } else { + this.createDatatarget(); + } + } + + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + updateDatatarget() { + this.resetErrors(); + this.counter = + 1 + + (this.payloadDeviceDatatarget?.length + ? this.payloadDeviceDatatarget?.length + : 0); + this.datatargetService.update(this.datatarget).subscribe( + (response: Datatarget) => { + this.datatarget = response; + if (this.datatarget.openDataDkDataset != null) { + this.datatarget.openDataDkDataset.acceptTerms = true; + } + this.shouldShowMailDialog().subscribe((response) => { + this.countToRedirect(); + }); + }, + (error: HttpErrorResponse) => { + this.checkDataTargetModelOpendatadkdatasaet(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + addPayloadDeviceDatatarget() { + this.payloadDeviceDatatarget.map((pdd) => { + if (pdd.payloadDecoderId === 0) { + pdd.payloadDecoderId = null; + } + }); + this.payloadDeviceDatatarget.forEach((relation) => { + if (relation.id) { + this.payloadDeviceDataTargetService.put(relation).subscribe( + (response) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } else { + this.payloadDeviceDataTargetService.post(relation).subscribe( + (res: any) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } + }); + } + + countToRedirect() { + this.counter -= 1; + if (this.counter <= 0 && !this.formFailedSubmit) { + this.showSavedSnack(); + this.routeToDatatargets(); + } + } + + getPayloadDeviceDatatarget(id: number) { + this.relationSubscription = this.payloadDeviceDataTargetService + .getByDataTarget(id) + .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { + this.mapToDatatargetDevicePayload(response); + }); + } + + createDatatarget() { + this.resetErrors(); + this.datatarget.applicationId = this.applicationId; + this.datatargetService.create(this.datatarget).subscribe( + (response: Datatarget) => { + this.datatargetid = response.id; + this.datatarget = response; + if (this.datatarget.openDataDkDataset != null) { + this.datatarget.openDataDkDataset.acceptTerms = true; + } + this.showSavedSnack(); + this.routeToDatatargets(); + }, + (error: HttpErrorResponse) => { + this.checkDataTargetModelOpendatadkdatasaet(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + + checkDataTargetModelOpendatadkdatasaet() { + if (!this.datatarget.openDataDkDataset) { + this.datatarget.openDataDkDataset = new OpenDataDkDataset(); + } + } + + getDevices(): void { + this.applicationSubscription = this.applicationService + .getApplication(this.applicationId) + .subscribe((application: Application) => { + this.devices = application.iotDevices; + }); + } + + public selectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map( + (device) => device.id + ); + } + + public deSelectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = []; + } + + getPayloadDecoders() { + this.payloadDecoderSubscription = this.payloadDecoderService + .getMultiple(1000, 0, 'id', 'ASC') + .subscribe((response: PayloadDecoderMappedResponse) => { + this.payloadDecoders = response.data; + }); + } + + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + + routeToDatatargets(): void { + this.router.navigate(['applications', this.applicationId.toString()]); + } + + onCoordinateKey(event: any) { + if (event.target.value.length > event.target.maxLength) { + event.target.value = event.target.value.slice(0, event.target.maxLength); + } + } + + getDatatarget(id: number) { + this.datatargetSubscription = this.datatargetService + .get(id) + .subscribe((response: Datatarget) => { + this.datatarget = response; + }); + } + + showSavedSnack() { + this.saveSnackService.showSavedSnack(); + } + + private setDataSetExcists() { + this.opendatadkService.get().subscribe((response) => { + this.dataSetExcists = response.dataset.length === 0 ? false : true; + }); + } + + private shouldShowMailDialog(): Observable { + return new Observable((observer) => { + if ( + !this.dataSetExcists && + this.datatarget.setToOpendataDk && + !this.isMailDialogAlreadyShown + ) { + this.isMailDialogAlreadyShown = true; + this.opendatadkDialogService.showDialog().subscribe((response) => { + if (response) { + this.showMailClient(); + } + observer.next(response); + }); + } else { + observer.next(true); + } + }); + } + + private showMailClient() { + if (!this.datatarget.openDataDkDataset.url) { + this.datatarget.openDataDkDataset.url = this.datatargetService.getOpendataSharingApiUrl(); + } + window.location.href = + 'mailto:FG2V@kk.dk?subject=Oprettelse%20af%20datas%C3%A6t%20i%20OpenDataDK&body=K%C3%A6re%20Frans%0D%0A%0D%0AHermed%20fremsendes%20linket%20til%20DCAT%20kataloget%20%2C%20du%20bedes%20registrere%20p%C3%A5%20Open%20Data%20DK%20platformen.%0D%0A%0D%0ALink%3A ' + + this.datatarget.openDataDkDataset.url; + } + + disableSaveButton(): boolean { + let disable = true; + if (!this.datatarget.setToOpendataDk) { + disable = false; + } else if (this.datatarget.openDataDkDataset?.acceptTerms) { + disable = false; + } else { + disable = true; + } + return disable; + } + + ngOnDestroy(): void { + if (this.relationSubscription) { + this.relationSubscription.unsubscribe(); + } + if (this.applicationSubscription) { + this.applicationSubscription.unsubscribe(); + } + if (this.datatargetSubscription) { + this.datatargetSubscription.unsubscribe(); + } + if (this.payloadDecoderSubscription) { + this.payloadDecoderSubscription.unsubscribe(); + } + } + + private mapToDatatargetDevicePayload( + dto: PayloadDeviceDatatargetGetByDataTargetResponse + ) { + this.payloadDeviceDatatarget = []; + dto.data.forEach((element) => { + this.payloadDeviceDatatarget.push({ + id: element.id, + iotDeviceIds: element.iotDevices.map((x) => x.id), + payloadDecoderId: + element.payloadDecoder?.id === undefined + ? 0 + : element.payloadDecoder?.id, + dataTargetId: element.dataTarget.id, + }); + }); + } +} diff --git a/src/app/shared/components/snack-bar/snack-bar.component.html b/src/app/shared/components/snack-bar/snack-bar.component.html new file mode 100644 index 00000000..aa504cd5 --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.html @@ -0,0 +1,9 @@ +
+ {{data.title}} +
+
+ {{data.message}} +
+
+ +
\ No newline at end of file diff --git a/src/app/shared/components/snack-bar/snack-bar.component.scss b/src/app/shared/components/snack-bar/snack-bar.component.scss new file mode 100644 index 00000000..29916f9b --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.scss @@ -0,0 +1,21 @@ +.snackbar-title-style { + font-weight: 500; + letter-spacing: 0.5px; +} +.snackbar-content-style { + letter-spacing: 0.5px; + border: none; + padding: 0 10px; + margin-top: 10px; + margin-bottom: 10px; +} +.snackbar-button-div-style { + text-align: center; + + .button-style { + letter-spacing: 0.5px; + border: 1px solid #000; + border-radius: 5px; + padding: 5px 15px; + } +} diff --git a/src/app/shared/components/snack-bar/snack-bar.component.ts b/src/app/shared/components/snack-bar/snack-bar.component.ts new file mode 100644 index 00000000..2bf27e0b --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Inject } from "@angular/core"; +import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from "@angular/material/snack-bar"; + + +@Component({ + selector: 'app-snackbar', + templateUrl: './snack-bar.component.html', + styleUrls: ['./snack-bar.component.scss'] +}) +export class SnackBarComponent implements OnInit { + + constructor( + @Inject(MAT_SNACK_BAR_DATA) public data: any, + public snackBarRef: MatSnackBarRef) {} + + ngOnInit(): void { + } +} diff --git a/src/app/shared/enums/datatarget-type.ts b/src/app/shared/enums/datatarget-type.ts index 5dea3827..795c317d 100644 --- a/src/app/shared/enums/datatarget-type.ts +++ b/src/app/shared/enums/datatarget-type.ts @@ -1,4 +1,5 @@ export enum DataTargetType { HTTPPUSH = 'HTTP_PUSH', - OPENDATADK = 'OPENDATADK' + OPENDATADK = 'OPENDATADK', + FIWARE = 'FIWARE' } diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts index d5831815..f759968f 100644 --- a/src/app/shared/services/snack.service.ts +++ b/src/app/shared/services/snack.service.ts @@ -1,6 +1,8 @@ import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; +import { SnackBarComponent } from '@shared/components/snack-bar/snack-bar.component'; + @Injectable({ providedIn: 'root', @@ -41,4 +43,18 @@ export class SnackService { duration: 10000, }); } + + public showSnackBar(title: string, displayMessage: string, buttonText: string) { + this.snackBar.openFromComponent(SnackBarComponent, { + data: { + title: title, + message: displayMessage, + buttonText: buttonText + }, + duration: 5000, + horizontalPosition: 'right', + verticalPosition: 'top', + panelClass: 'snackBar' + }); + } } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index bf9a16d8..af3bc49c 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -151,6 +151,8 @@ "DATATARGET": { "DETAILS": "Detaljer", "SAVE": "Gem datatarget", + "CREATE": "Opret", + "READMORE": "Læs mere", "DESCRIPTION": "Beskrivelse", "URL": "URL", "TIMEOUT": "Timeout", @@ -163,11 +165,17 @@ "NO-PAYLOADDECODER": "Ingen payload decoder", "IOTDEVICE": "IoT enhed(er)", "NO-RELATIONS": "ingen tilknyttede relationer", + "TENANT": "Tenant", + "NO-TENANT": "Ingen Tenant angivet", + "CONTEXT": "Context", + "NO-CONTEXT": "Ingen Context angivet", "AUTHORIZATIONHEADER": "Authorization header", "NO-AUTHORIZATIONHEADER": "Ingen Authorization header angivet", "ADD-TO-OPENDATADK": "Send data til OpenDataDK", - "OPENDATA-DK": "OpenDataDK", - "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu" + "OPENDATA-DK": "OpenDataDK", + "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu", + "HTTP_PUSH": "HTTP Push", + "FIWARE": "FIWARE" }, "MULTICAST": { "SAVE": "Gem multicast", @@ -945,5 +953,19 @@ "DELETE": "Slet" } }, - "NoUsersAdded": "Ingen brugergrupper er tilføjet" + "NoUsersAdded": "Ingen brugergrupper er tilføjet", + + "FIWARE": { + "QUESTION": { + "GIVE-DATATARGET-CONTEXTBROKER-URL": "Angiv datatargets 'context broker' URL", + "GIVE-DATATARGET-CONTEXTBROKER-URL-PLACEHOLDER": "https://contextBroker.dk/", + "GIVE-DATATARGET-TENANT": "Angiv 'tenant'", + "GIVE-DATATARGET-TENANT-INFO": "hvis tom, vil default tenant blive brugt", + "GIVE-DATATARGET-TENANT-PLACEHOLDER": "Tenant's navn", + "GIVE-DATATARGET-CONTEXT": "Angiv 'context'", + "GIVE-DATATARGET-CONTEXT-INFO": "hvis tom, skal den angives i 'payload'", + "GIVE-DATATARGET-CONTEXT-PLACEHOLDER": "https://os2iot/context-file.json" + } + + } } diff --git a/src/assets/images/logo_FIWARE.png b/src/assets/images/logo_FIWARE.png new file mode 100644 index 00000000..beb19973 Binary files /dev/null and b/src/assets/images/logo_FIWARE.png differ diff --git a/src/assets/images/logo_opendatadk.svg b/src/assets/images/logo_opendatadk.svg new file mode 100644 index 00000000..37964dfc --- /dev/null +++ b/src/assets/images/logo_opendatadk.svg @@ -0,0 +1 @@ +Open Data DK diff --git a/src/styles.scss b/src/styles.scss index 91b964e8..4455b654 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -53,3 +53,38 @@ font-family: 'roboto-bold'; src: url('./assets/fonts/Roboto-Bold.ttf') format('truetype'); } + +body { + // For Google Chrome + &::-webkit-scrollbar { + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: rgba($color: #000000, $alpha: 0.5); + } + + &::-webkit-scrollbar-track { + background: rgba($color: #000000, $alpha: 0.4); + } + + // For FireFox + scrollbar-width: 5px; + + // For Internet Explorer + & { + scrollbar-face-color: rgba($color: #000000, $alpha: 0.5); + scrollbar-track-color: rgba($color: #000000, $alpha: 0.4); + } +} + +.snackBar.mat-snack-bar-container{ + background: #f4f4f4; + color: #000; +} + +.mat-tooltip { + font-size: 1rem !important; +} +