From 99c31b677c7ecf9fc2c9aaffd3fc9f1af271bddb Mon Sep 17 00:00:00 2001 From: franzmueller Date: Tue, 12 Dec 2023 14:43:09 +0100 Subject: [PATCH] devices: show data usage --- .../shared/services/ladom.service.ts | 1 + .../device-instances.component.html | 9 +++++ .../device-instances.component.spec.ts | 7 +++- .../device-instances.component.ts | 34 ++++++++++++++++++- src/app/widgets/shared/export-data.service.ts | 28 +++++++++++++-- 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/app/modules/admin/permissions/shared/services/ladom.service.ts b/src/app/modules/admin/permissions/shared/services/ladom.service.ts index 6cfb0b83..412ddb95 100644 --- a/src/app/modules/admin/permissions/shared/services/ladom.service.ts +++ b/src/app/modules/admin/permissions/shared/services/ladom.service.ts @@ -142,6 +142,7 @@ export class LadonService { environment.costApiUrl, environment.costApiUrl + '/estimation/flow', environment.billingApiUrl + '/billing-components', + environment.timescaleAPIURL + '/usage' ] ServiceEndpoints.forEach(endpointURL => { diff --git a/src/app/modules/devices/device-instances/device-instances.component.html b/src/app/modules/devices/device-instances/device-instances.component.html index d9ac804e..816604c1 100644 --- a/src/app/modules/devices/device-instances/device-instances.component.html +++ b/src/app/modules/devices/device-instances/device-instances.component.html @@ -63,6 +63,15 @@ + + + Storage + + + {{formatBytes(getUsage(device)?.bytes || 0)}} + + + Shared diff --git a/src/app/modules/devices/device-instances/device-instances.component.spec.ts b/src/app/modules/devices/device-instances/device-instances.component.spec.ts index 943bddb3..5b63f3cf 100644 --- a/src/app/modules/devices/device-instances/device-instances.component.spec.ts +++ b/src/app/modules/devices/device-instances/device-instances.component.spec.ts @@ -31,6 +31,7 @@ import { createSpyFromClass, Spy } from 'jasmine-auto-spies'; import { DeviceInstancesService } from './shared/device-instances.service'; import { of } from 'rxjs'; import { DeviceTypeService } from '../../metadata/device-types-overview/shared/device-type.service'; +import { ExportDataService } from 'src/app/widgets/shared/export-data.service'; describe('DeviceInstancesComponent', () => { let component: DeviceInstancesComponent; @@ -45,6 +46,9 @@ describe('DeviceInstancesComponent', () => { const deviceTypeServiceSpy: Spy = createSpyFromClass(DeviceTypeService) deviceTypeServiceSpy.userHasPermSearchAuthorization.and.returnValue(true) + const exportDataServiceSpy: Spy = createSpyFromClass(ExportDataService); + exportDataServiceSpy.userHasUsageAuthroization.and.returnValue(false) + beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ @@ -62,7 +66,8 @@ describe('DeviceInstancesComponent', () => { { provide: KeycloakService, useClass: MockKeycloakService }, { provide: Router, useClass: RouterStub }, { provide: DeviceInstancesService, useValue: deviceInstanceServiceSpy }, - { provide: DeviceTypeService, useValue: deviceTypeServiceSpy } + { provide: DeviceTypeService, useValue: deviceTypeServiceSpy }, + { provide: ExportDataService, useValue: exportDataServiceSpy }, ], }).compileComponents(); }), diff --git a/src/app/modules/devices/device-instances/device-instances.component.ts b/src/app/modules/devices/device-instances/device-instances.component.ts index c8274af1..584c3a58 100644 --- a/src/app/modules/devices/device-instances/device-instances.component.ts +++ b/src/app/modules/devices/device-instances/device-instances.component.ts @@ -38,6 +38,7 @@ import { forkJoin, Observable, map, Subscription, of } from 'rxjs'; import { SearchbarService } from 'src/app/core/components/searchbar/shared/searchbar.service'; import { DeviceInstancesFilterDialogComponent } from './dialogs/device-instances-filter-dialog/device-instances-filter-dialog.component'; import { MatDialog } from '@angular/material/dialog'; +import { ExportDataService } from 'src/app/widgets/shared/export-data.service'; export interface DeviceInstancesRouterState { type: DeviceInstancesRouterStateTypesEnum | undefined | null; @@ -76,7 +77,8 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { private dialogsService: DialogsService, private deviceTypesService: DeviceTypeService, private searchbarService: SearchbarService, - private dialog: MatDialog + private dialog: MatDialog, + private exportDataService: ExportDataService, ) { this.getRouterParams(); } @@ -88,6 +90,11 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { offset = 0; ready = false; searchText = ''; + usage : { + deviceId: string; + updateAt: Date; + bytes: number; + }[] = []; @ViewChild('paginator', { static: false }) paginator!: MatPaginator; @@ -124,6 +131,10 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { } checkAuthorization() { + if (this.exportDataService.userHasUsageAuthroization()){ + this.displayedColumns.splice(4, 0, 'usage') + } + this.userHasUpdateAuthorization = this.deviceInstancesService.userHasUpdateAuthorization() if(this.userHasUpdateAuthorization) { this.displayedColumns.push("edit") @@ -218,6 +229,10 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { map((deviceInstances: DeviceInstancesModel[]) => { this.setDevices(deviceInstances); return deviceInstances + }), + map(d => { + this.exportDataService.getTimescaleDeviceUsage(d.map(di => di.id)).subscribe(r => this.usage.push(...r)) + return d }) ); } @@ -232,6 +247,7 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { this.ready = false; this.pageSize = 20; this.selectionClear(); + this.usage = []; forkJoin(this.load(), this.getTotalCount()).subscribe({ next: (_) => this.ready = true, @@ -342,4 +358,20 @@ export class DeviceInstancesComponent implements OnInit, AfterViewInit { }); }); } + + getUsage(d: DeviceInstancesModel) { + return this.usage.find(u => u.deviceId === d.id); + } + + formatBytes(bytes: number, decimals = 2) { + if (!+bytes) return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` + } } diff --git a/src/app/widgets/shared/export-data.service.ts b/src/app/widgets/shared/export-data.service.ts index 92927006..fb179192 100644 --- a/src/app/widgets/shared/export-data.service.ts +++ b/src/app/widgets/shared/export-data.service.ts @@ -26,13 +26,18 @@ import { } from './export-data.model'; import {HttpClient} from '@angular/common/http'; import {map} from 'rxjs/operators'; - +import { PermissionTestResponse } from 'src/app/modules/admin/permissions/shared/permission.model'; +import { LadonService } from 'src/app/modules/admin/permissions/shared/services/ladom.service'; @Injectable({ providedIn: 'root', }) export class ExportDataService { - constructor(private http: HttpClient) { + usageAuthorizations: PermissionTestResponse + + constructor(private http: HttpClient, private ladonService: LadonService) { + this.usageAuthorizations = this.ladonService.getUserAuthorizationsForURI(environment.billingApiUrl + '/billing-components') + } getLastValuesInflux(requestElements: LastValuesRequestElementInfluxModel[]): Observable { @@ -120,4 +125,23 @@ export class ExportDataService { return res; })); } + + userHasUsageAuthroization(): boolean { + return this.usageAuthorizations["POST"] + } + + getTimescaleDeviceUsage(deviceIds: string[]): Observable<{ + deviceId: string; + updateAt: Date; + bytes: number; + }[]> { + return this.http.post<{ + deviceId: string; + updateAt: Date; + bytes: number; + }[]>(environment.timescaleAPIURL + '/usage/devices', deviceIds).pipe(map(res => { + res.forEach(r => r.updateAt = new Date(r.updateAt)) + return res; + })) + } }