diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index 1eac588d..ff4a356a 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -1,6 +1,5 @@
@@ -18,22 +17,68 @@

Detaljer

-
-
-
-
- - + + +
+
+
+
+
+ + +
+ + + + + +
+
- - - - -
-
-
+ + +
+
+
+
+
+ + +
+ + + + + +
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index bd2c124d..288804b8 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -9,6 +9,7 @@ import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Application } from '@applications/application.model'; import { ApplicationService } from '@applications/application.service'; +import { environment } from '@environments/environment'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; import { DeviceType } from '@shared/enums/device-type'; @@ -28,6 +29,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { public application: Application; public backButton: BackButton = { label: '', routerLink: '/applications' }; public id: number; + public pageLimit = environment.tablePageSize; public dropdownButton: DropdownButton; public errorMessage: string; public canEdit = false; diff --git a/src/app/applications/applications-routing.module.ts b/src/app/applications/applications-routing.module.ts index f563dfab..d5dcd3eb 100644 --- a/src/app/applications/applications-routing.module.ts +++ b/src/app/applications/applications-routing.module.ts @@ -7,9 +7,10 @@ import { ApplicationsComponent } from './applications.component'; import { IoTDeviceDetailComponent } from './iot-devices/iot-device-detail/iot-device-detail.component'; import { IotDeviceEditComponent } from './iot-devices/iot-device-edit/iot-device-edit.component'; import { DatatargetEditComponent } from './datatarget/datatarget-edit/datatarget-edit.component'; -import { DatatargetListComponent } from './datatarget/datatarget-list/datatarget-list.component'; import { DatatargetDetailComponent } from './datatarget/datatarget-detail/datatarget-detail.component'; 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'; const applicationRoutes: Routes = [ @@ -26,18 +27,17 @@ 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: 'datatarget-list/:name', - children: [ - { path: '', component: DatatargetListComponent }, - { path: 'datatarget-edit', component: DatatargetEditComponent }, - { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, - { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent } - ] + { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, - }, - { path: 'bulk-import', component: BulkImportComponent } + { path: 'datatarget-edit', component: DatatargetEditComponent }, + { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, + { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, + + { path: 'multicast-edit', component: MulticastEditComponent}, + { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, + { path: 'multicast/:multicastId', component: MulticastDetailComponent }, + + { path: 'bulk-import', component: BulkImportComponent }, ], }, diff --git a/src/app/applications/applications.module.ts b/src/app/applications/applications.module.ts index 69f3114c..acc30053 100644 --- a/src/app/applications/applications.module.ts +++ b/src/app/applications/applications.module.ts @@ -17,34 +17,35 @@ import { NGMaterialModule } from '@shared/Modules/materiale.module'; import { BulkImportComponent } from './bulk-import/bulk-import.component'; import { PipesModule } from '@shared/pipes/pipes.module'; import { ApplicationsTableComponent } from './applications-list/applications-table/applications-table.component'; - +import { MulticastModule } from './multicast/multicast.module'; @NgModule({ - declarations: [ - ApplicationsComponent, - ApplicationDetailComponent, - ApplicationEditComponent, - ApplicationsListComponent, - ApplicationsTableComponent, - BulkImportComponent, - ], - exports: [ - ApplicaitonsRoutingModule, - ApplicationsComponent, - ApplicationsTableComponent, - ], - imports: [ - CommonModule, - RouterModule, - TranslateModule, - IotDevicesModule, - DatatargetModule, - DirectivesModule, - FormModule, - SharedModule, - FontAwesomeModule, - NGMaterialModule, - PipesModule, - ], + declarations: [ + ApplicationsComponent, + ApplicationDetailComponent, + ApplicationEditComponent, + ApplicationsListComponent, + ApplicationsTableComponent, + BulkImportComponent, + ], + exports: [ + ApplicaitonsRoutingModule, + ApplicationsComponent, + ApplicationsTableComponent, + ], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + IotDevicesModule, + DatatargetModule, + DirectivesModule, + FormModule, + SharedModule, + FontAwesomeModule, + NGMaterialModule, + PipesModule, + MulticastModule, + ], }) -export class ApplicationsModule { } +export class ApplicationsModule {} 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 7af25889..af38e9d9 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts @@ -11,7 +11,6 @@ import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dia import { Datatarget } from '../datatarget.model'; import { DropdownButton } from '@shared/models/dropdown-button.model'; import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; -import { IotDevice } from '@applications/iot-devices/iot-device.model'; @Component({ selector: 'app-datatarget-detail', @@ -65,7 +64,7 @@ export class DatatargetDetailComponent implements OnInit, OnDestroy { } private setBackButton(applicationId: number) { - this.backButton.routerLink = ['applications', applicationId.toString(), 'datatarget-list', this.applicationName ] + this.backButton.routerLink = ['applications', applicationId.toString()] } onDeleteDatatarget() { 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 968df9e2..fa1f81f9 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts @@ -11,7 +11,7 @@ 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 { SaveSnackService } from '@shared/services/save-snack.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'; @@ -44,7 +44,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { public formFailedSubmit = false; public datatargetid: number; private applicationId: number; - private applicationNane: string; + private applicationName: string; public application: Application; public devices: IotDevice[]; public payloadDecoders = []; @@ -63,7 +63,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { private applicationService: ApplicationService, private payloadDecoderService: PayloadDecoderService, private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, - private saveSnackService: SaveSnackService, + private saveSnackService: SnackService, private dialog: MatDialog, private errorMessageService: ErrorMessageService, private opendatadkService: OpendatadkService, @@ -94,7 +94,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); this.applicationId = +this.route.snapshot.paramMap.get('id'); - this.applicationNane = this.route.snapshot.paramMap.get('name'); + this.applicationName = this.route.snapshot.paramMap.get('name'); if (this.datatargetid !== 0) { this.getDatatarget(this.datatargetid); this.getPayloadDeviceDatatarget(this.datatargetid); @@ -237,6 +237,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { this.datatarget.openDataDkDataset.acceptTerms = true; } this.showSavedSnack(); + this.routeToDatatargets(); }, (error: HttpErrorResponse) => { this.checkDataTargetModelOpendatadkdatasaet(); @@ -288,7 +289,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { } routeToDatatargets(): void { - this.router.navigate(['applications',this.applicationId.toString(),'datatarget-list', this.applicationNane]) + this.router.navigate(['applications',this.applicationId.toString()]) } onCoordinateKey(event: any) { diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html deleted file mode 100644 index bad1cea6..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
-
-
-
- -
-
-
-
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts deleted file mode 100644 index 7b515efa..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DatatargetListComponent } from './datatarget-list.component'; - -describe('DatatargetListComponent', () => { - let component: DatatargetListComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ DatatargetListComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DatatargetListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts deleted file mode 100644 index 2f5236cd..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { ActivatedRoute } from '@angular/router'; -import { Datatarget } from '../datatarget.model'; -import { BackButton } from '@shared/models/back-button.model'; -import { environment } from '@environments/environment'; -import { Title } from '@angular/platform-browser'; - - -@Component({ - selector: 'a[app-datatarget-list]', - templateUrl: './datatarget-list.component.html', - styleUrls: ['./datatarget-list.component.scss'] -}) -export class DatatargetListComponent implements OnInit { - - public pageLimit = environment.tablePageSize; - public title: string; - public backButton: BackButton = { label: '', routerLink: ''}; - public datatarget: Datatarget; - private applikationId: string; - - constructor( - public translate: TranslateService, - private titleService: Title, - private route: ActivatedRoute) { - translate.use('da'); - } - - ngOnInit(): void { - const applikationName: string = this.route.snapshot.paramMap.get('name'); - this.applikationId = this.route.snapshot.paramMap.get('id'); - this.translate.get(["NAV.DATATARGET", "NAV.APPLICATIONS", "TITLE.DATATARGET"]) - .subscribe((translate) => { - this.title = translate['NAV.DATATARGET'] + ' - ' + applikationName; - this.backButton.label = translate['NAV.APPLICATIONS']; - this.titleService.setTitle(translate['TITLE.DATATARGET']); - }); - this.setBackButton() - } - - setBackButton() { - this.backButton.routerLink = ['applications', this.applikationId]; - } - - updatePageLimit(limit: any) { - console.log(limit); - } -} 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 43de08b9..c839d784 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html @@ -15,7 +15,7 @@ - + {{ 'DATATARGET-TABLE.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 c3bbc0df..fa4eb35d 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts @@ -46,7 +46,7 @@ export class DatatargetTableComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit(): void { - this.applicationId = +Number(this.route.parent.parent.snapshot.paramMap.get('id')); + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); console.log(this.applicationId); this.getDatatarget(); this.canEdit = this.meService.canWriteInTargetOrganization() diff --git a/src/app/applications/datatarget/datatarget.module.ts b/src/app/applications/datatarget/datatarget.module.ts index 58bd4078..46be0a33 100644 --- a/src/app/applications/datatarget/datatarget.module.ts +++ b/src/app/applications/datatarget/datatarget.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DatatargetTableComponent } from './datatarget-table/datatarget-table.component'; -import { DatatargetListComponent } from './datatarget-list/datatarget-list.component'; import { DatatargetEditComponent } from './datatarget-edit/datatarget-edit.component'; import { DatatargetDetailComponent } from './datatarget-detail/datatarget-detail.component'; import { TranslateModule } from '@ngx-translate/core'; @@ -19,7 +18,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; @NgModule({ declarations: [ DatatargetTableComponent, - DatatargetListComponent, DatatargetEditComponent, DatatargetDetailComponent, OpendatadkComponent, @@ -39,7 +37,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; ], exports: [ DatatargetTableComponent, - DatatargetListComponent, DatatargetEditComponent, DatatargetDetailComponent, NGMaterialModule diff --git a/src/app/applications/datatarget/datatarget.service.ts b/src/app/applications/datatarget/datatarget.service.ts index 72abdb98..74e3688c 100644 --- a/src/app/applications/datatarget/datatarget.service.ts +++ b/src/app/applications/datatarget/datatarget.service.ts @@ -66,7 +66,7 @@ export class DatatargetService { return datatarget; } ) - );; + ); } delete(id: number) { diff --git a/src/app/applications/iot-devices/downlink.model.ts b/src/app/applications/iot-devices/downlink.model.ts index 6c5f1b8b..b5ed5035 100644 --- a/src/app/applications/iot-devices/downlink.model.ts +++ b/src/app/applications/iot-devices/downlink.model.ts @@ -1,6 +1,5 @@ - export class Downlink { - data: string; - port = 0; - confirmedDownlink = false; + data: string; + port = 0; + confirmedDownlink? = false; } diff --git a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts index ac550bb7..c2ece359 100644 --- a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts +++ b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts @@ -20,7 +20,6 @@ import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-d import { DeviceType } from '@shared/enums/device-type'; import { MatDialog } from '@angular/material/dialog'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; -import { ReceivedMessageMetadata } from '@shared/models/received-message-metadata.model'; import { environment } from '@environments/environment'; import { startWith, switchMap, map, catchError } from 'rxjs/operators'; import { MeService } from '@shared/services/me.service'; diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html new file mode 100644 index 00000000..919ff162 --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html @@ -0,0 +1,137 @@ +
+ +
+
+
+

{{ 'MULTICAST.BASIC-DETAILS' | translate }}

+ +

+ {{ 'MULTICAST.GROUPNAME' | translate }}{{ multicast.name | translate }} +

+ +

{{ 'MULTICAST.LORAWAN-DETAILS' | translate }}

+ +

+ {{ 'MULTICAST.ADDRESS' | translate }}{{ multicast.mcAddr | translate }} +

+

+ {{ 'MULTICAST.NETWORK-KEY' | translate }}{{ multicast.mcNwkSKey | translate }} +

+

+ {{ 'MULTICAST.APPLICATION-KEY' | translate }}{{ multicast.mcAppSKey | translate }} +

+

+ {{ 'MULTICAST.FRAMECOUNTER' | translate }}{{ multicast.fCnt }} +

+

+ {{ 'MULTICAST.DATARATE' | translate }}{{ multicast.dr }} +

+

+ {{ 'MULTICAST.FREQUENCY' | translate }}{{ multicast.frequency }} +

+

+ {{ 'MULTICAST.GROUPTYPE' | translate }}{{ multicast.groupType }} +

+ +
+
+ +
+
+

{{ 'APPLICATION.ATTACHED-IOT' | translate }}

+
+

+ {{ 'MULTICAST.IOTDEVICE' | translate }}: + + , + {{ device.name }} + +

+
+
+
+
+
+

Downlink

+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss b/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss new file mode 100644 index 00000000..ea3f31bc --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss @@ -0,0 +1,3 @@ +.loraDetails{ + margin-top: 15px; +} \ No newline at end of file diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts new file mode 100644 index 00000000..a2a43859 --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -0,0 +1,179 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { Subscription } from 'rxjs'; +import { Multicast } from '../multicast.model'; +import { Location } from '@angular/common'; +import { MulticastService } from '../multicast.service'; +import { SnackService } from '@shared/services/snack.service'; +import { Downlink } from '@applications/iot-devices/downlink.model'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { MatDialog } from '@angular/material/dialog'; +import { DownlinkDialogComponent } from '@applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component'; +import { keyPressedHex } from '@shared/constants/regex-constants'; +import { DownlinkService } from '@shared/services/downlink.service'; + +@Component({ + selector: 'app-multicast-detail', + templateUrl: './multicast-detail.component.html', + styleUrls: ['./multicast-detail.component.scss'], +}) +export class MulticastDetailComponent implements OnInit, OnDestroy { + public multicast: Multicast; + public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + public formFailedSubmit = false; + private applicationId: number; + public downlink = new Downlink(); + @Input() errorMessages: string[]; + + constructor( + private route: ActivatedRoute, + private dialog: MatDialog, + private deleteDialogService: DeleteDialogService, + private location: Location, + private multicastService: MulticastService, + private translate: TranslateService, + private snackService: SnackService, + private errorMessageService: ErrorMessageService, + private downlinkService: DownlinkService + ) {} + + ngOnInit(): void { + this.errorMessages = []; + const id: number = +this.route.snapshot.paramMap.get('multicastId'); + if (id) { + this.getMulticast(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../multicast-edit/' + id, + isErasable: true, + }; + this.applicationId = +this.route.snapshot.paramMap.get('id'); + } + this.translate + .get(['GEN.BACK', 'MULTICAST-TABLE-ROW.SHOW-OPTIONS']) + .subscribe((translations) => { + this.backButton.label = translations['GEN.BACK']; + this.dropdownButton.label = + translations['MULTICAST-TABLE-ROW.SHOW-OPTIONS']; + }); + } + + getMulticast(id: number) { + this.multicastService.get(id).subscribe((multicast: Multicast) => { + this.multicast = multicast; + this.setBackButton(this.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = ['applications', applicationId.toString()]; + } + + // Class-B: + // only if classB can be used + // canShowPeriodicity(): boolean { + // if (this.multicast.groupType === MulticastType.ClassB) { + // return true; + // } else return false; + // } + + onDeleteMulticast() { + this.deleteDialogSubscription = this.deleteDialogService + .showSimpleDialog() + .subscribe((response) => { + if (response) { + this.multicastService + .delete(this.multicast.id) + .subscribe((response) => { + if (response.status !== 0) { + this.snackService.showDeletedSnack(); + this.location.back(); + } else { + this.snackService.showFailSnack(); + } + }); + } else { + } + }); + } + + keyPressHexadecimal(event) { + // make sure only hexadecimal can be typed in input with adresses. + keyPressedHex(event); + } + + private handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorMessages = errors.errorFields; + this.errorMessages = errors.errorMessages; + } + + clickDownlink() { + if (this.multicast.iotDevices.length > 0) { + if (this.validateHex(this.downlink.data)) { + this.multicastService + .multicastGet(this.multicast.id) + .subscribe((response: any) => { + if (response.totalCount > 0) { + this.openDownlinkDialog(); + } else { + this.startDownlink(); + } + }); + } + } else { + this.downlinkService.showSendDownlinkFailNoDevices(); + } + } + openDownlinkDialog() { + const dialog = this.dialog.open(DownlinkDialogComponent, {}); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.startDownlink(); + } + }); + } + + private startDownlink() { + this.errorMessages = []; + this.multicastService + .multicastPost(this.downlink, this.multicast.id) + .subscribe( + () => { + this.snackService.showInQueueSnack(); + }, + (error) => { + this.handleError(error); + } + ); + } + + private validateHex(input: string): boolean { + const isHexinput = /^[a-fA-F\d]+$/.test(input); + + if (isHexinput) { + return true; + } else { + this.addToErrorMessage('MULTICAST.DOWNLINK.NO-PORT-OR-PAYLOAD'); + return false; + } + } + + addToErrorMessage(text: string) { + this.translate.get([text]).subscribe((translations) => { + this.errorMessages.push(translations[text]); + }); + } + + ngOnDestroy(): void { + this.deleteDialogSubscription?.unsubscribe(); + } +} diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html new file mode 100644 index 00000000..42e456dc --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -0,0 +1,256 @@ + + +
+
+
    +
  • + {{ error | translate }} +
  • +
+
+ +
+
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + + + {{ multicastType }} + + +
+ + + +
+ +
+ + +
+ + {{ + 'QUESTION.MULTICAST.SELECT-DEVICES' | translate + }} + + + + + + + + + {{ device.name }} + + +
+
+
+ + +
+
diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss b/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss new file mode 100644 index 00000000..74d588da --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss @@ -0,0 +1,5 @@ +.onlyLorawan { + font-weight: bold; + font-size: 13px; + margin-left: 10px; +} diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts new file mode 100644 index 00000000..4bdde2f0 --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -0,0 +1,207 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { MulticastType } from '@shared/enums/multicast-type'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { SnackService } from '@shared/services/snack.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { ReplaySubject, Subject, Subscription } from 'rxjs'; +import { Multicast } from '../multicast.model'; +import { MulticastService } from '../multicast.service'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { ApplicationService } from '@applications/application.service'; +import { keyPressedHex } from '@shared/constants/regex-constants'; +import { FormControl } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-multicast-edit', + templateUrl: './multicast-edit.component.html', + styleUrls: ['./multicast-edit.component.scss'], +}) +export class MulticastEditComponent implements OnInit, OnDestroy { + public title: string; + public multicastId: number; + public errorMessages: unknown; + private multicastSubscription: Subscription; + public searchDevices: FormControl = new FormControl(); + public errorFields: string[]; + public iotDevices: IotDevice[] = []; + @Input() submitButton: string; + public backButtonTitle: string; + public multicast: Multicast = new Multicast(); + private applicationId: number; + private onDestroy = new Subject(); + public formFailedSubmit = false; + public multicastTypes: string[] = Object.values(MulticastType); + // Class-B: { public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; // used for classB if it has to be used in the future } + public deviceFilterCtrl: FormControl = new FormControl(); + public filteredDevicesMulti: ReplaySubject = new ReplaySubject< + IotDevice[] + >(1); + + constructor( + private translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private multicastService: MulticastService, + private errorMessageService: ErrorMessageService, + private scrollToTopService: ScrollToTopService, + private snackService: SnackService, + private applicationService: ApplicationService + ) {} + + ngOnInit(): void { + this.translate + .get([ + 'FORM.CREATE-NEW-MULTICAST', + 'FORM.EDIT-MULTICAST', + 'MULTICAST.SAVE', + 'NAV.MULTICAST', + 'GEN.BACK', + ]) + .subscribe((translations) => { + this.multicastId = +this.route.snapshot.paramMap.get('multicastId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + + if (this.multicastId) { + this.title = translations['FORM.EDIT-MULTICAST']; + } else { + this.title = translations['FORM.CREATE-NEW-MULTICAST']; + } + this.submitButton = translations['MULTICAST.SAVE']; + this.backButtonTitle = translations['GEN.BACK']; + }); + + this.getApplication(this.applicationId); + + if (this.multicastId) { + // If edit is pressed, then get the specific multicast. + this.getMulticast(this.multicastId); + } + + this.deviceFilterCtrl.valueChanges + .pipe(takeUntil(this.onDestroy)) + .subscribe(() => { + this.filterDevicesMulti(); + }); + } + + private filterDevicesMulti() { + if (!this.iotDevices) { + return; + } + // get the search keyword + let search = this.deviceFilterCtrl?.value?.trim(); + if (!search) { + this.filteredDevicesMulti.next(this.iotDevices.slice()); + return; + } else { + search = search.toLowerCase(); + } + const filtered = this.iotDevices.filter((device) => { + return device.name.toLocaleLowerCase().indexOf(search) > -1; + }); + this.filteredDevicesMulti.next(filtered); + } + + onSubmit(): void { + if (this.multicastId) { + // if already created, only update + this.updateMulticast(); + } else { + // else create new + this.createMulticast(); + } + } + + getMulticast(id: number) { + this.multicastSubscription = this.multicastService + .get(id) + .subscribe((response: Multicast) => { + this.multicast = response; // gets the multicast and set's local multicast. Used when update. + }); + } + + getApplication(id: number) { + this.applicationService.getApplication(id).subscribe((application) => { + this.iotDevices = application.iotDevices; + this.filteredDevicesMulti.next(this.iotDevices.slice()); + }); + } + + // only if classB can be used + // showPeriodicity(): boolean { + // if (this.multicast.groupType === MulticastType.ClassB) { + // return true; + // } else return false; + // } + + updateMulticast(): void { + this.resetErrors(); + this.multicast.applicationID = this.applicationId; + + this.multicastService.update(this.multicast).subscribe( + () => { + this.snackService.showUpdatedSnack(); + this.routeBack(); + }, + (error: HttpErrorResponse) => { + this.snackService.showFailSnack(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + createMulticast(): void { + this.resetErrors(); + this.multicast.applicationID = this.applicationId; + + this.multicastService.create(this.multicast).subscribe( + () => { + this.snackService.showSavedSnack(); + this.routeBack(); + }, + (error: HttpErrorResponse) => { + this.snackService.showFailSnack(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + public compare( + o1: IotDevice | undefined, + o2: IotDevice | undefined + ): boolean { + return o1?.id === o2?.id; + } + + selectAll() { + this.multicast.iotDevices = this.iotDevices; + } + unSelectAll() { + this.multicast.iotDevices = []; + } + + routeBack(): void { + this.router.navigate(['applications', this.applicationId.toString()]); + } + keyPressHexadecimal(event) { + keyPressedHex(event); + } + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + ngOnDestroy(): void { + this.multicastSubscription?.unsubscribe(); + } +} diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts new file mode 100644 index 00000000..16d385f6 --- /dev/null +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -0,0 +1,29 @@ +import { Application } from '@applications/application.model'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { MulticastType } from '@shared/enums/multicast-type'; + +export class MulticastResponse { + id: number; + application: Application; + iotDevices: IotDevice[]; + groupName: string; + lorawanMulticastDefinition: LorawanMulticastDefinition; + // periodicity: number; -> only if classB is gonna be used + createdAt: string; + updatedAt: string; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; +} + +export class LorawanMulticastDefinition { + address: string; + networkSessionKey: string; + applicationSessionKey: string; + frameCounter = 0; + dataRate = 0; + frequency = 0; + groupType: MulticastType; + chirpstackGroupId?: string; +} diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html new file mode 100644 index 00000000..2b35a5f4 --- /dev/null +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -0,0 +1,53 @@ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'MULTICAST-TABLE.NAME' | translate }} + + {{element.groupName}} + + {{ 'MULTICAST-TABLE.TYPE' | translate }} + + {{element.lorawanMulticastDefinition.groupType}} + +
+ + +
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.scss b/src/app/applications/multicast/multicast-table/multicast-table.component.scss similarity index 100% rename from src/app/applications/datatarget/datatarget-list/datatarget-list.component.scss rename to src/app/applications/multicast/multicast-table/multicast-table.component.scss diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts new file mode 100644 index 00000000..54225d5c --- /dev/null +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -0,0 +1,133 @@ +import { + AfterViewInit, + Component, + Input, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { ActivatedRoute } from '@angular/router'; +import { environment } from '@environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { MeService } from '@shared/services/me.service'; +import { SnackService } from '@shared/services/snack.service'; +import { merge, Observable, Subscription, of as observableOf } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { Multicast, MulticastData } from '../multicast.model'; +import { MulticastService } from '../multicast.service'; + +@Component({ + selector: 'app-multicast-table', + templateUrl: './multicast-table.component.html', + styleUrls: ['./multicast-table.component.scss'], +}) +export class MulticastTableComponent + implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + displayedColumns: string[] = ['groupName', 'groupType', 'menu']; + multicasts: Multicast[] = []; + resultsLength = 0; + public canEdit = false; + @Input() isLoadingResults = true; + public pageSize = environment.tablePageSize; + public pageOffset = 0; + public applicationId: number; + + private multicastSubscription: Subscription; + private deleteDialogSubscription: Subscription; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private multicastService: MulticastService, + private meService: MeService, + public translate: TranslateService, + public snackService: SnackService + ) { + translate.use('da'); + } + + ngOnInit(): void { + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); + this.canEdit = this.meService.canWriteInTargetOrganization(); + } + + ngAfterViewInit() { + // If the user changes the sort order, reset back to the first page. + this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + const multicasts = this.getMulticasts( + this.sort.active, + this.sort.direction + ); + return multicasts; + }), + map((data) => { + // Flip flag to show that loading has finished. + if (data.ok === false) { + this.snackService.showLoadFailSnack(); + } + this.isLoadingResults = false; + this.resultsLength = data.count; + + return data.data; + }), + catchError(() => { + this.isLoadingResults = false; + return observableOf([]); + }) + ) + .subscribe((data) => (this.multicasts = data)); + } + + getMulticasts( + orderByColumn: string, + orderByDirection: string + ): Observable { + if (this.applicationId) { + return this.multicastService.getMulticastsByApplicationId( + this.paginator.pageSize, + this.paginator.pageIndex * this.paginator.pageSize, + orderByDirection, + orderByColumn, + this.applicationId + ); + } + } + + deleteMulticast(multicast: Multicast) { + this.deleteDialogSubscription = this.deleteDialogService + .showSimpleDialog() + .subscribe((response) => { + if (response) { + // if user presses "yes, delete", then delete the multicast. + this.multicastService.delete(multicast.id).subscribe((response) => { + if (response.ok && response.body.affected > 0) { + // if deleted succesfully, get the new array of multicasts and show a succesful snack. + this.paginator.page.emit({ + pageIndex: this.paginator.pageIndex, + pageSize: this.paginator.pageSize, + length: this.resultsLength, + }); + this.snackService.showDeletedSnack(); + } else { + this.snackService.showFailSnack(); + } + }); + } + }); + } + ngOnDestroy() { + this.multicastSubscription?.unsubscribe(); + this.deleteDialogSubscription?.unsubscribe(); + } +} diff --git a/src/app/applications/multicast/multicast.model.ts b/src/app/applications/multicast/multicast.model.ts new file mode 100644 index 00000000..1dec442c --- /dev/null +++ b/src/app/applications/multicast/multicast.model.ts @@ -0,0 +1,29 @@ +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { MulticastType } from '@shared/enums/multicast-type'; + +export class Multicast { + id: number; + applicationID: number; + iotDevices?: IotDevice[]; + name: string; + mcAddr: string; + mcNwkSKey: string; + mcAppSKey: string; + fCnt: number = 0; + dr: number = 0; + frequency: number = 0; + groupType: MulticastType; + // periodicity: number; -> only if classB is gonna be used + createdAt: string; + updatedAt: string; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; +} + +export class MulticastData { + data: Multicast[]; + ok?: boolean; + count?: number; +} diff --git a/src/app/applications/multicast/multicast.module.ts b/src/app/applications/multicast/multicast.module.ts new file mode 100644 index 00000000..e56d5271 --- /dev/null +++ b/src/app/applications/multicast/multicast.module.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { MulticastDetailComponent } from './multicast-detail/multicast-detail.component'; +import { MulticastEditComponent } from './multicast-edit/multicast-edit.component'; +import { MulticastTableComponent } from './multicast-table/multicast-table.component'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { NGMaterialModule } from '@shared/Modules/materiale.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { PipesModule } from '@shared/pipes/pipes.module'; +import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-select-search.module'; + +@NgModule({ + declarations: [ + MulticastDetailComponent, + MulticastEditComponent, + MulticastTableComponent, + ], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + NGMaterialModule, + FontAwesomeModule, + ReactiveFormsModule, + FormsModule, + SharedModule, + PipesModule, + MatSelectSearchModule, + ], + exports: [ + MulticastDetailComponent, + MulticastEditComponent, + MulticastTableComponent, + ], +}) +export class MulticastModule {} diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts new file mode 100644 index 00000000..8dd061de --- /dev/null +++ b/src/app/applications/multicast/multicast.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@angular/core'; +import { UserMinimalService } from '@app/admin/users/user-minimal.service'; +import { Downlink } from '@applications/iot-devices/downlink.model'; +import { RestService } from '@shared/services/rest.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { MulticastResponse } from './multicast-response.model'; +import { Multicast, MulticastData } from './multicast.model'; + +@Injectable({ + providedIn: 'root', +}) +export class MulticastService { + constructor( + private restService: RestService, + private userMinimalService: UserMinimalService + ) {} + + private multicastURL = 'multicast'; + private multicastDownlinkURL = 'multicast/'; + private DOWNLINKMULTICASTURL = 'downlink-multicast'; + + getMulticastsByApplicationId( + limit: number, + offset: number, + sort: string, + orderOn: string, + applicationId: number + ): Observable { + const body = { + limit, + offset, + sort, + orderOn, + applicationId, + }; + return this.restService.get(this.multicastURL, body); + } + + get(id: number): Observable { + return this.restService.get(this.multicastURL, {}, id).pipe( + // bind "this" correctly by creating a new lambda function + map((response: MulticastResponse) => { + const multicast = this.mapToMulticast(response); + return multicast; + }) + ); + } + + delete(id: number) { + return this.restService.delete(this.multicastURL, id); + } + update(multicast: Multicast): Observable { + return this.restService.put(this.multicastURL, multicast, multicast.id); + } + create(multicast: Multicast): Observable { + return this.restService.post(this.multicastURL, multicast); + } + + private mapToMulticast(multicastResponse: MulticastResponse): Multicast { + const model: Multicast = { + id: multicastResponse.id, + name: multicastResponse.groupName, + groupType: multicastResponse.lorawanMulticastDefinition.groupType, + mcAddr: multicastResponse.lorawanMulticastDefinition.address, + mcAppSKey: + multicastResponse.lorawanMulticastDefinition.applicationSessionKey, + dr: multicastResponse.lorawanMulticastDefinition.dataRate, + fCnt: multicastResponse.lorawanMulticastDefinition.frameCounter, + frequency: multicastResponse.lorawanMulticastDefinition.frequency, + mcNwkSKey: multicastResponse.lorawanMulticastDefinition.networkSessionKey, + applicationID: multicastResponse.application.id, + iotDevices: multicastResponse.iotDevices, + createdAt: multicastResponse.createdAt, + updatedAt: multicastResponse.updatedAt, + createdBy: multicastResponse.createdBy, + updatedBy: multicastResponse.updatedBy, + createdByName: this.userMinimalService.getUserNameFrom( + multicastResponse.createdBy + ), + updatedByName: this.userMinimalService.getUserNameFrom( + multicastResponse.updatedBy + ), + }; + return model; + } + + public multicastGet(multicastId: number, params = {}): Observable { + const url = + this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.get(url, params); + } + public multicastPost( + downlink: Downlink, + multicastId: number, + params = {} + ): Observable { + const url = + this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.post(url, downlink, params); + } +} diff --git a/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts b/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts index cf7462e0..e4bb105b 100644 --- a/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts +++ b/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts @@ -18,7 +18,7 @@ import { DeviceModelService } from '@app/device-model/device-model.service'; import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; import { TestPayloadDecoder } from '@payload-decoder/test-payload-decoder.model'; import { TestPayloadDecoderService } from '@payload-decoder/test-payload-decoder.service'; -import { SaveSnackService } from '@shared/services/save-snack.service'; +import { SnackService } from '@shared/services/snack.service'; import { ErrorMessageService } from '@shared/error-message.service'; import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; import { environment } from '@environments/environment'; @@ -75,7 +75,7 @@ export class PayloadDecoderEditComponent implements OnInit { private sharedVariableService: SharedVariableService, private iotDeviceService: IoTDeviceService, private deviceModelService: DeviceModelService, - private saveSnackService: SaveSnackService, + private saveSnackService: SnackService, private errorMessageService: ErrorMessageService, private scrollToTopService: ScrollToTopService, ) { } diff --git a/src/app/shared/components/forms/form-body-application/form-body-application.component.ts b/src/app/shared/components/forms/form-body-application/form-body-application.component.ts index f1eb19a5..556e82d4 100644 --- a/src/app/shared/components/forms/form-body-application/form-body-application.component.ts +++ b/src/app/shared/components/forms/form-body-application/form-body-application.component.ts @@ -89,8 +89,7 @@ export class FormBodyApplicationComponent implements OnInit, OnDestroy { this.applicationService .createApplication(this.application) .subscribe( - (response) => { - console.log(response); + () => { this.router.navigateByUrl('/applications'); }, (error: HttpErrorResponse) => { diff --git a/src/app/shared/constants/regex-constants.ts b/src/app/shared/constants/regex-constants.ts new file mode 100644 index 00000000..ef7a76a5 --- /dev/null +++ b/src/app/shared/constants/regex-constants.ts @@ -0,0 +1,10 @@ +export function keyPressedHex(event) { + var inp = String.fromCharCode(event.keyCode); + + if (/[a-fA-F0-9]/.test(inp)) { + return true; + } else { + event.preventDefault(); + return false; + } +} diff --git a/src/app/shared/enums/multicast-type.ts b/src/app/shared/enums/multicast-type.ts new file mode 100644 index 00000000..acb0283d --- /dev/null +++ b/src/app/shared/enums/multicast-type.ts @@ -0,0 +1,4 @@ +export enum MulticastType { + // Class-B: {ClassB = 'Class-B'}, + ClassC = 'CLASS_C', +} diff --git a/src/app/shared/pipes/filter-devices.pipe.ts b/src/app/shared/pipes/filter-devices.pipe.ts new file mode 100644 index 00000000..47edb0bf --- /dev/null +++ b/src/app/shared/pipes/filter-devices.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { DeviceType } from '@shared/enums/device-type'; + +@Pipe({ + name: 'filterDevices', +}) +export class FilterDevicesPipe implements PipeTransform { + transform(value: IotDevice[] | undefined, ..._: unknown[]): IotDevice[] { + // Filter devices so only LoRaWAN devices will be shown. + const lorawanDevices: IotDevice[] = []; + + if (!value) { + return lorawanDevices; + } + value.forEach((device) => { + if (device.type === DeviceType.LORAWAN) { + lorawanDevices.push(device); + } + }); + + lorawanDevices.sort((a, b) => a.name.localeCompare(b.name)); + + return lorawanDevices; + } +} diff --git a/src/app/shared/pipes/pipes.module.ts b/src/app/shared/pipes/pipes.module.ts index a3e26445..c5aacc28 100644 --- a/src/app/shared/pipes/pipes.module.ts +++ b/src/app/shared/pipes/pipes.module.ts @@ -5,26 +5,27 @@ import { ActiveDeactivePipe } from './activeDeactive.pipe'; import { isGlobalAdminPipe } from './is-global-admin.pipe'; import { CreatedUpdatedByPipe } from './created-updated-by.pipe'; import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; +import { FilterDevicesPipe } from './filter-devices.pipe'; @NgModule({ - declarations: [ - isGlobalAdminPipe, - ActiveDeactivePipe, - YesNoPipe, - CustomDatePipe, - CustomTableDatePipe, - CreatedUpdatedByPipe, - ], - imports: [ - CommonModule - ], - exports: [ - isGlobalAdminPipe, - ActiveDeactivePipe, - YesNoPipe, - CustomDatePipe, - CustomTableDatePipe, - CreatedUpdatedByPipe, - ] + declarations: [ + isGlobalAdminPipe, + ActiveDeactivePipe, + YesNoPipe, + CustomDatePipe, + CustomTableDatePipe, + CreatedUpdatedByPipe, + FilterDevicesPipe, + ], + imports: [CommonModule], + exports: [ + isGlobalAdminPipe, + ActiveDeactivePipe, + YesNoPipe, + CustomDatePipe, + CustomTableDatePipe, + CreatedUpdatedByPipe, + FilterDevicesPipe, + ], }) -export class PipesModule { } +export class PipesModule {} diff --git a/src/app/shared/services/downlink.service.ts b/src/app/shared/services/downlink.service.ts index 0715afbe..edbf8cfe 100644 --- a/src/app/shared/services/downlink.service.ts +++ b/src/app/shared/services/downlink.service.ts @@ -2,24 +2,42 @@ import { Injectable } from '@angular/core'; import { RestService } from './rest.service'; import { Observable } from 'rxjs'; import { Downlink } from '@applications/iot-devices/downlink.model'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class DownlinkService { - private IOTDEVICEURL = 'iot-device/'; private DOWNLINKURL = 'downlink'; - constructor(private restService: RestService) { } + constructor( + private restService: RestService, + private snackBar: MatSnackBar, + public translate: TranslateService + ) {} public get(deviceId: number, params = {}): Observable { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.get(url, params); } - public post(downlink: Downlink, deviceId: number, params = {}): Observable { + public post( + downlink: Downlink, + deviceId: number, + params = {} + ): Observable { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.post(url, downlink, params); } + public showSendDownlinkFailNoDevices() { + this.snackBar.open( + this.translate.instant('SNACK.NODEVICES'), + this.translate.instant('SNACK.CLOSE'), + { + duration: 10000, + } + ); + } } diff --git a/src/app/shared/services/save-snack.service.ts b/src/app/shared/services/save-snack.service.ts deleted file mode 100644 index fcee59d7..00000000 --- a/src/app/shared/services/save-snack.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; - - -@Injectable({ - providedIn: 'root', -}) -export class SaveSnackService { - constructor( - private snackBar: MatSnackBar) { } - - public showSavedSnack() { - this.snackBar.open('Gemt Succesfuldt', 'Luk', { - duration: 10000, - }); - } -} diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts new file mode 100644 index 00000000..d5831815 --- /dev/null +++ b/src/app/shared/services/snack.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root', +}) +export class SnackService { + constructor( + private snackBar: MatSnackBar, + public translate: TranslateService + ) {} + + public showSavedSnack() { + this.snackBar.open(this.translate.instant('SNACK.SAVE'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showDeletedSnack() { + this.snackBar.open(this.translate.instant('SNACK.DELETE'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showUpdatedSnack() { + this.snackBar.open(this.translate.instant('SNACK.UPDATE'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showFailSnack() { + this.snackBar.open(this.translate.instant('SNACK.FAIL'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showLoadFailSnack() { + this.snackBar.open(this.translate.instant('SNACK.LOADFAIL'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showInQueueSnack() { + this.snackBar.open(this.translate.instant('SNACK.QUEUE'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 10f666f3..1c086680 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -17,6 +17,7 @@ "ALL-IOT-DEVICES": "Alle IoT enheder", "LORA-GATEWAYS": "LoRaWAN gateways", "DATATARGET": "Datatarget", + "MULTICAST": "Multicast", "MY-DATATARGET": "Tilbage", "DATATARGET-APPLIKATION": "for applikation:", "PROFILES": "LoRaWAN profiler", @@ -45,6 +46,16 @@ "DELETE": "Slet" } }, + "SNACK": { + "SAVE": "Gemt succesfuldt", + "DELETE": "Slettet succesfuldt", + "UPDATE": "Opdateret succesfuldt", + "FAIL": "Fejl - aktion ikke fuldført", + "LOADFAIL": "Fejl - kunne ikke loade", + "QUEUE": "Element sat i kø", + "CLOSE": "Luk", + "NODEVICES": "Kan ikke sende downlink - der er ingen devices!" + }, "SEARCH": { "ICON": "", "TYPE": "Type", @@ -86,7 +97,10 @@ "DESCRIPTION": "Applikationens beskrivelse", "ATTACHED-IOT": "Tilknyttede IoT enheder", "DATATARGET-SHOW": "Tilknyttede data targets", + "MULTICAST-SHOW": "Tilknyttede multicast", + "MULTICAST-GROUPS": "Multicast-grupper", "IMPORT-CSV": "Bulk import af IoT enheder", + "IOT-DEVICES": "IoT-enheder", "BULK": { "TEMPLATE": { "GENERIC": "Generic HTTP sample", @@ -154,6 +168,26 @@ "OPENDATA-DK": "OpenDataDK", "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu" }, + "MULTICAST": { + "SAVE": "Gem multicast", + "BASIC-DETAILS": "Basale detaljer", + "LORAWAN-DETAILS": "LoRaWAN detailjer", + "GROUPNAME": "Gruppe navn", + "ADDRESS": "Adresse", + "NETWORK-KEY": "Network session key", + "APPLICATION-KEY": "Network application key", + "FRAMECOUNTER": "Frame counter", + "DATARATE": "Data rate", + "FREQUENCY": "Frekvens (Hz)", + "GROUPTYPE": "Gruppe type", + "PERIODICITY": "Periodicitet", + "IOTDEVICE": "IoT enheder", + "DOWNLINK": { + "PORT": "Angiv den ønskede port", + "PAYLOAD": "Angiv det ønskede payload", + "START": "Sæt downlink i kø" + } + }, "OPENDATADK": { "QUESTION": { "GIVE-OPENDATADK-NAME": "Datasæt titel", @@ -241,6 +275,10 @@ "NAME": "Navn", "TYPE": "Type" }, + "MULTICAST-TABLE":{ + "NAME": "Gruppenavn", + "TYPE": "Gruppetype" + }, "IOT-DEVICE-TYPES": { "GENERIC_HTTP": "Generisk HTTP", "LORAWAN": "LoRaWAN", @@ -265,7 +303,13 @@ }, "DATATARGET-TABLE-ROW": { "DELETE": "Slet datatarget", - "EDIT": "Redigér" + "EDIT": "Redigér", + "SHOW-OPTIONS": "Håndter datatarget" + }, + "MULTICAST-TABLE-ROW": { + "DELETE": "Slet multicast", + "EDIT": "Redigér", + "SHOW-OPTIONS": "Håndter multicast" }, "PAYLOAD-DECODER": { "DELETE-FAILED": "Slet fejlede", @@ -365,7 +409,9 @@ "CREATE-NEW-LORA-GATEWAY": "Opret ny LoRaWAN gateway", "CREATE-NEW-DEVICE-MODEL": "Opret ny device model", "CREATE-NEW-DATATARGET": "Opret nyt datatarget", + "CREATE-NEW-MULTICAST": "Opret nyt multicast", "EDIT-DATATARGET": "Redigér datatarget", + "EDIT-MULTICAST": "Redigér multicast", "CREATE-NEW-IOT-DEVICE": "Tilføj en IoT enhed", "EDIT-NEW-GATEWAY": "Redigér LoRaWAN gateway", "EDIT-NEW-APPLICATION": "Redigér applikation", @@ -427,6 +473,23 @@ "GIVE-PAYLOADDECODER-PAYLOAD-INVALID-JSON": "Det angivne JSON var ikke gyldigt i feltet payload", "GIVE-ORGANISATION-NAME": "Navngiv organisation", "GIVE-ORGANISATION-NAME-PLACEHOLDER": "F.eks. 'Aarhus Kommune'", + "GIVE-MULTICAST-NAME":"Navngiv multicast", + "GIVE-MULTICAST-ADDRESS": "Angiv multicast adressen", + "GIVE-MULTICAST-NETWORK-KEY": "Angiv multicast network session key", + "GIVE-MULTICAST-APPLICATION-KEY": "Angiv multicast application session key", + "GIVE-MULTICAST-FRAMECOUNTER": "Angiv frame counter", + "GIVE-MULTICAST-DATARATE": "Angiv data rate", + "GIVE-MULTICAST-IOTDEVICES": "Angiv IoT Devices", + "GIVE-MULTICAST-FREQUENCY": "Angiv frekvens (Hz)", + "GIVE-MULTICAST-GROUP-TYPE": "Angiv multicast gruppe type", + "GIVE-MULTICAST-PERIODICITY": "Angiv multicast ping-slot periodicitet", + "GIVE-MULTICAST-GROUPTYPE": "Angiv multicast gruppe typen", + "MULTICAST-NAME-PLACEHOLDER": "Multicastens navn", + "MULTICAST-ADDRESS-PLACEHOLDER": "Multicast adressen", + "MULTICAST-NETWORK-KEY-PLACEHOLDER": "Multicastens network session key", + "MULTICAST-APPLICATION-KEY-PLACEHOLDER": "Multicastens application session key", + "MULTICAST-GROUPTYPE-PLACEHOLDER": "Vælg multicast gruppe typen", + "MULTICAST-PERIODICITY-PLACEHOLDER": "Vælg Class-B ping periodiciteten", "OTAAAPPLICATIONKEY": "OTAA application key (AppKey)", "OTAAAPPLICATIONKEY-PLACEHOLDER": "Indtast OTAA application key", "DEVADDR": "Device adress", @@ -459,6 +522,13 @@ "DESELECTALLDEVICES": "Fravælg alle", "RELATIONS": "*Efter oprettelse af dit data target parres dette med en/flere payload decoder(s) og IoT-devices" }, + "MULTICAST": { + "SELECT-DEVICES": "Vælg enheder", + "SELECTALLDEVICES": "Vælg alle", + "DESELECTALLDEVICES": "Fravælg alle", + "NO-PORT-OR-PAYLOAD": "Angiv en port og en payload", + "ONLY-LORAWAN": "* På nuværende tidspunkt er det kun muligt at tilknytte LoRaWAN devices." + }, "SIGFOX": { "TITLE": "Sigfox specifikke felter", "CONNECTTOEXISTINGDEVICEINBACKEND": "Er enheden allerede registreret i Sigfox backend?", @@ -631,7 +701,9 @@ "DELETE-NOT-ALLOWED-HAS-SIGFOX-DEVICE": "Applikation kan ikke blive slettet, da den indeholder en eller flere Sigfox enheder.", "DELETE-NOT-ALLOWED-HAS-LORAWAN-DEVICE": "Service profilen kan ikke blive slettet, da den er i brug af en eller flere LoRaWAN enheder.", "OTAA-INFO-MISSING": "OTAA nøgle mangler eller er ikke gyldig.", - "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig." + "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig.", + "DIFFERENT-SERVICE-PROFILE": "Dine devices har forskellige service profiles. De skal have den samme service profile!", + "WRONG-SERVICE-PROFILE": "Dine devices har forkert service profile. Vælg devices som har samme service profile som din multicast." }, "PROFILES": { "NAME": "LoRaWAN profiler", @@ -698,7 +770,7 @@ "PINGSLOTFREQ": "Class-B ping-slot frequency", "SUPPORTSCLASSC_ACTIVATE": "Device supports Class-C", "CLASSCTIMEOUT": "Class C confirmed downlink timeout", - "CANCEL": "Anuller", + "CANCEL": "Annuller", "SAVE": "Gem", "OTAA-ABP": "Join (OTAA / ABP)", "MACVERSION_PLACEHOLDER": "1.0.0", @@ -822,6 +894,7 @@ "PERMISSION": "OS2IoT - Brugergrupper", "ORGANIZATION": "OS2IoT - Organisationer", "DATATARGET": "OS2IoT - Datatarget", + "MULTICAST": "OS2IoT - Multicast", "BULKIMPORT": "OS2IoT - Bulk import", "IOTDEVICE": "OS2IoT - IoT enhed", "FRONTPAGE": "OS2IoT - Forside"