diff --git a/src/app/admin/organisation/organisation.service.ts b/src/app/admin/organisation/organisation.service.ts index 856a84c9..014300d0 100644 --- a/src/app/admin/organisation/organisation.service.ts +++ b/src/app/admin/organisation/organisation.service.ts @@ -77,6 +77,20 @@ export class OrganisationService { }); } + getMultipleWithGatewayAdmin( + limit: number = 1000, + offset: number = 0, + orderByColumn?: string, + orderByDirection?: string + ): Observable { + return this.restService.get(`${this.URL}/gatewayAdmin`, { + limit, + offset, + orderOn: orderByColumn, + sort: orderByDirection, + }); + } + delete(id: number) { return this.restService.delete(this.URL, id); } diff --git a/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.html b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.html new file mode 100644 index 00000000..b6a3160b --- /dev/null +++ b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.html @@ -0,0 +1,27 @@ +
+

{{ "GATEWAY.CHANGE-ORGANIZATION.TITLE" | translate }}

+
+ + + + {{ organization.name }} + + +
+
+ + +
+
diff --git a/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.scss b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.scss new file mode 100644 index 00000000..6d9e23f4 --- /dev/null +++ b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.scss @@ -0,0 +1,3 @@ +.gateway-change-organization-dialog { + width: 50vw; +} diff --git a/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.ts b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.ts new file mode 100644 index 00000000..0f685632 --- /dev/null +++ b/src/app/gateway/gateway-change-organization-dialog/gateway-change-organization-dialog.component.ts @@ -0,0 +1,72 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { Organisation } from "@app/admin/organisation/organisation.model"; +import { OrganisationService } from "@app/admin/organisation/organisation.service"; +import { TranslateService } from "@ngx-translate/core"; +import { GatewayDialogModel } from "@shared/models/dialog.model"; +import { ChirpstackGatewayService } from "@shared/services/chirpstack-gateway.service"; +import { SharedVariableService } from "@shared/shared-variable/shared-variable.service"; +import { ReplaySubject, Subscription } from "rxjs"; +import { UpdateGatewayOrganization } from "../gateway.model"; + +@Component({ + selector: "app-gateway-change-organization-dialog", + templateUrl: "./gateway-change-organization-dialog.component.html", + styleUrls: ["./gateway-change-organization-dialog.component.scss"], +}) +export class GatewayChangeOrganizationDialogComponent implements OnInit { + public gatewaysSubscription: Subscription; + public organizationsSubscription: Subscription; + public gateway: UpdateGatewayOrganization; + public organizations: Organisation[]; + public filteredOrganizations: ReplaySubject = new ReplaySubject(1); + + constructor( + private gatewayService: ChirpstackGatewayService, + public translate: TranslateService, + private organizationService: OrganisationService, + private sharedVariableService: SharedVariableService, + private snackBar: MatSnackBar, + private dialog: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public dialogModel: GatewayDialogModel + ) { + this.gateway = { + organizationId: this.dialogModel.organizationId ?? this.sharedVariableService.getSelectedOrganisationId(), + }; + } + + ngOnInit(): void { + this.translate.use("da"); + this.getOrganizations(); + } + + getOrganizations() { + this.organizationsSubscription = this.organizationService.getMultipleWithGatewayAdmin().subscribe(res => { + this.organizations = res.data; + this.filteredOrganizations.next(this.organizations.slice()); + }); + } + + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + onSubmit() { + this.gatewaysSubscription = this.gatewayService + .updateGatewayOrganization(this.gateway, this.dialogModel.gatewayDbId) + .subscribe(gateway => { + this.snackBar.open( + this.translate.instant("GATEWAY.CHANGE-ORGANIZATION.SNACKBAR-SAVED", { + gatewayName: gateway.name, + organizationName: gateway.organization.name, + }), + "", + { + duration: 10000, + } + ); + this.dialog.close(true); + }); + } +} diff --git a/src/app/gateway/gateway-detail/gateway-detail.component.ts b/src/app/gateway/gateway-detail/gateway-detail.component.ts index a55fbea4..e20a77b0 100644 --- a/src/app/gateway/gateway-detail/gateway-detail.component.ts +++ b/src/app/gateway/gateway-detail/gateway-detail.component.ts @@ -16,6 +16,9 @@ import { ChartConfiguration } from "chart.js"; import { ColorGraphBlue1 } from "@shared/constants/color-constants"; import { formatDate } from "@angular/common"; import { DefaultPageSizeOptions } from "@shared/constants/page.constants"; +import { MatDialog } from "@angular/material/dialog"; +import { GatewayChangeOrganizationDialogComponent } from "../gateway-change-organization-dialog/gateway-change-organization-dialog.component"; +import { GatewayDialogModel } from "@shared/models/dialog.model"; @Component({ selector: "app-gateway-detail", @@ -50,7 +53,8 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit private translate: TranslateService, private router: Router, private meService: MeService, - private deleteDialogService: DeleteDialogService + private deleteDialogService: DeleteDialogService, + private changeOrganizationDialog: MatDialog ) {} ngOnInit(): void { @@ -120,9 +124,18 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit label: "LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS", editRouterLink: "../../gateway-edit/" + this.gatewayId, isErasable: true, + extraOptions: [], } : null; + this.translate.get("GATEWAY.CHANGE-ORGANIZATION.TITLE").subscribe(translation => { + this.dropdownButton.extraOptions.push({ + id: this.gatewayId, + label: translation, + onClick: () => this.onOpenChangeOrganizationDialog(), + }); + }); + this.translate.get(["LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS"]).subscribe(translations => { if (this.dropdownButton) { this.dropdownButton.label = translations["LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS"]; @@ -188,6 +201,21 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit }); } + onOpenChangeOrganizationDialog() { + const dialog = this.changeOrganizationDialog.open(GatewayChangeOrganizationDialogComponent, { + data: { + gatewayDbId: this.gateway.id, + organizationId: this.gateway.organizationId, + } as GatewayDialogModel, + }); + + dialog.afterClosed().subscribe(res => { + if (!res) return; + + location.reload(); + }); + } + ngOnDestroy() { if (this.gatewaySubscription) { this.gatewaySubscription.unsubscribe(); diff --git a/src/app/gateway/gateway-table/gateway-table.component.html b/src/app/gateway/gateway-table/gateway-table.component.html index b72ce8a5..5e70d251 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.html +++ b/src/app/gateway/gateway-table/gateway-table.component.html @@ -206,6 +206,11 @@ "GEN.EDIT" | translate }} + diff --git a/src/app/gateway/gateway-table/gateway-table.component.ts b/src/app/gateway/gateway-table/gateway-table.component.ts index 813fa956..bfc00650 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.ts +++ b/src/app/gateway/gateway-table/gateway-table.component.ts @@ -14,6 +14,9 @@ import { OrganizationAccessScope } from "@shared/enums/access-scopes"; import { DefaultPageSizeOptions } from "@shared/constants/page.constants"; import { TableColumn } from "@shared/types/table.type"; import { catchError, map, startWith, switchMap } from "rxjs/operators"; +import { MatDialog } from "@angular/material/dialog"; +import { GatewayDialogModel } from "@shared/models/dialog.model"; +import { GatewayChangeOrganizationDialogComponent } from "../gateway-change-organization-dialog/gateway-change-organization-dialog.component"; const columnDefinitions: TableColumn[] = [ { @@ -138,7 +141,8 @@ export class GatewayTableComponent implements AfterViewInit, OnDestroy, OnInit { public translate: TranslateService, private meService: MeService, private deleteDialogService: DeleteDialogService, - private cdRef: ChangeDetectorRef + private cdRef: ChangeDetectorRef, + private changeOrganizationDialog: MatDialog ) { this.translate.use("da"); moment.locale("da"); @@ -254,5 +258,19 @@ export class GatewayTableComponent implements AfterViewInit, OnDestroy, OnInit { }); } + onOpenChangeOrganizationDialog(id: number) { + const dialog = this.changeOrganizationDialog.open(GatewayChangeOrganizationDialogComponent, { + data: { + gatewayDbId: id, + } as GatewayDialogModel, + }); + + dialog.afterClosed().subscribe(res => { + if (!res) return; + + location.reload(); + }); + } + protected readonly columnDefinitions = columnDefinitions; } diff --git a/src/app/gateway/gateway.model.ts b/src/app/gateway/gateway.model.ts index 090b6154..fc070dd4 100644 --- a/src/app/gateway/gateway.model.ts +++ b/src/app/gateway/gateway.model.ts @@ -1,6 +1,7 @@ import { EditPermission } from "@shared/models/edit-permission.model"; import { CommonLocation } from "@shared/models/common-location.model"; import { GatewayPlacement, GatewaySetupStatus, GatewayStatusInterval } from "./enums/gateway-status-interval.enum"; +import { Organisation } from "@app/admin/organisation/organisation.model"; export class Gateway extends EditPermission { id: number; @@ -26,6 +27,7 @@ export class Gateway extends EditPermission { lastSeenAt: Date; organizationId: number; organizationName: string; + organization: Organisation; createdAt: Date; updatedAt: Date; createdBy: number; @@ -93,3 +95,7 @@ export interface AllGatewayStatusResponse { data: GatewayStatus[]; count: number; } + +export class UpdateGatewayOrganization { + public organizationId: number; +} diff --git a/src/app/gateway/gateway.module.ts b/src/app/gateway/gateway.module.ts index 16283bb2..337b176a 100644 --- a/src/app/gateway/gateway.module.ts +++ b/src/app/gateway/gateway.module.ts @@ -18,6 +18,7 @@ import { GraphModule } from "@app/graph/graph.module"; import { GatewayListComponent } from "./gateway-overview/gateway-tabs/gateway-list/gateway-list.component"; import { GatewayMapComponent } from "./gateway-overview/gateway-tabs/gateway-map/gateway-map.component"; import { GatewayStatusOverviewComponent } from "./gateway-overview/gateway-tabs/gateway-status-overview/gateway-status-overview.component"; +import { GatewayChangeOrganizationDialogComponent } from "./gateway-change-organization-dialog/gateway-change-organization-dialog.component"; const gatewayRoutes: Routes = [ { @@ -51,6 +52,7 @@ const gatewayRoutes: Routes = [ GatewayListComponent, GatewayMapComponent, GatewayStatusOverviewComponent, + GatewayChangeOrganizationDialogComponent, ], imports: [ CommonModule, diff --git a/src/app/shared/models/dialog.model.ts b/src/app/shared/models/dialog.model.ts index 7cd890ff..4551ba36 100644 --- a/src/app/shared/models/dialog.model.ts +++ b/src/app/shared/models/dialog.model.ts @@ -17,3 +17,8 @@ export class ApplicationDialogModel { applicationId: number; organizationId?: number; } + +export class GatewayDialogModel { + gatewayDbId: number; + organizationId?: number; +} diff --git a/src/app/shared/services/chirpstack-gateway.service.ts b/src/app/shared/services/chirpstack-gateway.service.ts index 3776a532..98cc3e2f 100644 --- a/src/app/shared/services/chirpstack-gateway.service.ts +++ b/src/app/shared/services/chirpstack-gateway.service.ts @@ -1,7 +1,14 @@ import { Injectable } from "@angular/core"; import { RestService } from "./rest.service"; import { Observable } from "rxjs"; -import { GatewayResponse, Gateway, GatewayData, GatewayRequest, GatewayResponseMany } from "@app/gateway/gateway.model"; +import { + GatewayResponse, + Gateway, + GatewayData, + GatewayRequest, + GatewayResponseMany, + UpdateGatewayOrganization, +} from "@app/gateway/gateway.model"; import moment from "moment"; import { SharedVariableService } from "@shared/shared-variable/shared-variable.service"; import { map } from "rxjs/operators"; @@ -69,6 +76,10 @@ export class ChirpstackGatewayService { return this.restService.put(this.chripstackGatewayUrl, gatewayRequest, id); } + public updateGatewayOrganization(body: UpdateGatewayOrganization, id: number): Observable { + return this.restService.put(`${this.chripstackGatewayUrl}/updateGatewayOrganization`, body, id); + } + public delete(gatewayId: string): Observable { return this.restService.delete(this.chripstackGatewayUrl, gatewayId); } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 61245de5..40769beb 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -197,6 +197,11 @@ "PROJECT": "Projekt", "PROTOTYPE": "Prototype", "OTHER": "Andet" + }, + "CHANGE-ORGANIZATION": { + "TITLE": "Skift organisation", + "CHOOSE-ORGANIZATION": "Vælg hvilken organisation denne gateway skal tilhøre", + "SNACKBAR-SAVED": "{{gatewayName}} tilhører nu {{organizationName}}" } }, "IOT-DEVICE": {