Skip to content

Commit

Permalink
Merge pull request #175 from OS2iot/feature/IOT-1530_datatarget_log
Browse files Browse the repository at this point in the history
Feature/iot 1530 datatarget log
  • Loading branch information
Fritsen authored Sep 24, 2024
2 parents 7ed576b + 865c5a2 commit a5ab360
Show file tree
Hide file tree
Showing 50 changed files with 1,040 additions and 665 deletions.
34 changes: 22 additions & 12 deletions src/app/applications/applications-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { DatatargetTabComponent } from "@applications/datatarget/datatarget-tab/datatarget-tab.component";
import { IotDeviceDataPacketsTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-data-packets-tab/iot-device-data-packets-tab.component";
import { IotDeviceDetailsTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-details-tab/iot-device-details-tab.component";
import { IotDeviceDownlinkTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-downlink-tab/iot-device-downlink-tab.component";
import { IotDeviceHistoryTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-history-tab/iot-device-history-tab.component";
import { IotDevicesTabComponent } from "@applications/iot-devices/iot-devices-tab/iot-devices-tab.component";
import { MulticastTabComponent } from "@applications/multicast/multicast-tab/multicast-tab.component";
import { ApplicationDetailComponent } from "./application-detail/application-detail.component";
import { ApplicationEditComponent } from "./application-edit/application-edit.component";
import { ApplicationsListComponent } from "./applications-list/applications-list.component";
import { ApplicationsComponent } from "./applications.component";
import { BulkImportComponent } from "./bulk-import/bulk-import.component";
import { DatatargetDetailComponent } from "./datatarget/datatarget-detail/datatarget-detail.component";
import { DatatargetEditComponent } from "./datatarget/datatarget-edit/datatarget-edit.component";
import { DatatargetLogComponent } from "./datatarget/datatarget-log/datatarget-log.component";
import { DatatargetNewComponent } from "./datatarget/datatarget-new/datatarget-new.component";
import { FiwareDetailComponent } from "./datatarget/fiware/fiware-detail/fiware-detail.component";
import { HttppushDetailComponent } from "./datatarget/httppush/httppush-detail/httppush-detail.component";
import { MqttDetailComponent } from "./datatarget/mqtt/mqtt-detail/mqtt-detail.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 { 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";
import { DatatargetNewComponent } from "./datatarget/datatarget-new/datatarget-new.component";
import { IotDevicesTabComponent } from "@applications/iot-devices/iot-devices-tab/iot-devices-tab.component";
import { MulticastTabComponent } from "@applications/multicast/multicast-tab/multicast-tab.component";
import { DatatargetTabComponent } from "@applications/datatarget/datatarget-tab/datatarget-tab.component";
import { IotDeviceDetailsTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-details-tab/iot-device-details-tab.component";
import { IotDeviceHistoryTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-history-tab/iot-device-history-tab.component";
import { IotDeviceDataPacketsTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-data-packets-tab/iot-device-data-packets-tab.component";
import { IotDeviceDownlinkTabComponent } from "@applications/iot-devices/iot-device-detail/iot-device-downlink-tab/iot-device-downlink-tab.component";
import { MulticastEditComponent } from "./multicast/multicast-edit/multicast-edit.component";

const applicationRoutes: Routes = [
{
Expand Down Expand Up @@ -70,6 +74,12 @@ const applicationRoutes: Routes = [
{
path: "datatarget/:datatargetId",
component: DatatargetDetailComponent,
children: [
{ path: "datatarget-log", component: DatatargetLogComponent },
{ path: "httppush-detail", component: HttppushDetailComponent },
{ path: "fiware-detail", component: FiwareDetailComponent },
{ path: "mqtt-detail", component: MqttDetailComponent },
],
},
{ path: "multicast-edit", component: MulticastEditComponent },
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Location } from "@angular/common";
import { HttpResponse } from "@angular/common/http";
import { ActivatedRoute, Router } from "@angular/router";
import { DatatargetDetail } from "@applications/datatarget/datatarget-detail/datatarget-detail";
import { Datatarget } from "@applications/datatarget/datatarget.model";
import { DatatargetService } from "@applications/datatarget/datatarget.service";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { TranslateService } from "@ngx-translate/core";
import { DeleteDialogService } from "@shared/components/delete-dialog/delete-dialog.service";
import { OrganizationAccessScope } from "@shared/enums/access-scopes";
import { BackButton } from "@shared/models/back-button.model";
import { DropdownButton } from "@shared/models/dropdown-button.model";
import { MeService } from "@shared/services/me.service";
import { NavTab } from "@shared/types/nav-tabs.type";
import { Subscription } from "rxjs";

export abstract class DatatargetDetailTabsCommon implements DatatargetDetail {
protected abstract getDetailsLink(): string;

faExclamationTriangle = faExclamationTriangle;

logLink: "datatarget-log" = "datatarget-log";
navTabs: NavTab[] = [
{
label: "APPLICATION.DETAILS",
link: this.getDetailsLink(),
index: 0,
},
{
label: "GEN.LOG",
link: this.logLink,
index: 1,
},
];

datatarget: Datatarget;
backButton: BackButton = { label: "", routerLink: undefined };
dropdownButton: DropdownButton;
canEdit: boolean;

private subscriptions: Subscription[] = [];
private deleteDialogSubscription: Subscription;

constructor(
route: ActivatedRoute,
router: Router,
translate: TranslateService,
meService: MeService,
private location: Location,
private datatargetService: DatatargetService,
public deleteDialogService: DeleteDialogService
) {
// Load first tab if none was selected
const path = this.location.path();
if (!this.navTabs.some(tab => path.includes(tab.link))) {
router.navigate([path, this.navTabs[0].link], { replaceUrl: true });
}
// URL params
const paramMap = route.snapshot.paramMap;
const id: number = +paramMap.get("datatargetId");
const appId: number = +paramMap.get("id");
if (id) {
// Fetch datatarget info
this.subscriptions.push(this.getDatatarget(id));
this.dropdownButton = {
label: "",
editRouterLink: "../../datatarget-edit/" + id,
isErasable: true,
};
}
// Translate button labels
this.subscriptions.push(
translate.get(["NAV.MY-DATATARGET", "DATATARGET.SHOW-OPTIONS"]).subscribe(translations => {
this.backButton.label = translations["NAV.MY-DATATARGET"];
this.dropdownButton.label = translations["DATATARGET.SHOW-OPTIONS"];
})
);
// Check user permissions
this.canEdit = meService.hasAccessToTargetOrganization(OrganizationAccessScope.ApplicationWrite, undefined, appId);
}

protected onDestroy(): void {
this.subscriptions?.forEach(s => s?.unsubscribe());
this.deleteDialogSubscription?.unsubscribe();
}

private getDatatarget = (id: number) =>
this.datatargetService.get(id).subscribe((dataTarget: Datatarget) => {
this.datatarget = dataTarget;
});

onDeleteDatatarget() {
this.deleteDialogSubscription?.unsubscribe();
this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe(response => {
// Do nothing if user cancels
if (!response) return;
this.subscriptions.push(
this.datatargetService.delete(this.datatarget.id).subscribe((deleteResponse: HttpResponse<any>) => {
if (deleteResponse?.ok) {
this.location.back();
} else {
// TODO: Show error / snackbar??
console.log("Delete failed", deleteResponse);
}
})
);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, ComponentFactoryResolver, OnDestroy, OnInit, Type, ViewChild } from "@angular/core";
import { Component, OnDestroy, Type, ViewChild } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { DataTargetType } from "@shared/enums/datatarget-type";
import { Subscription } from "rxjs";
import { DatatargetTypesService } from "../datatarget-types.service";
import { Datatarget } from "../datatarget.model";
import { DatatargetService } from "../datatarget.service";
Expand All @@ -12,18 +13,31 @@ import { DatatargetDetailTypeSelectorDirective } from "./datatarget-detail-type-
templateUrl: "./datatarget-detail.component.html",
styleUrls: ["./datatarget-detail.component.scss"],
})
export class DatatargetDetailComponent implements OnInit, OnDestroy {
export class DatatargetDetailComponent implements OnDestroy {
@ViewChild(DatatargetDetailTypeSelectorDirective, { static: true })
adHost!: DatatargetDetailTypeSelectorDirective;

public datatarget: Datatarget;
private datatargetType: DataTargetType;

private datatargetSubscription: Subscription;

constructor(
private datatargetService: DatatargetService,
private route: ActivatedRoute,
private datatargetTypesService: DatatargetTypesService
) {}
datatargetService: DatatargetService,
route: ActivatedRoute,
datatargetTypesService: DatatargetTypesService
) {
const id: number = +route.snapshot.paramMap.get("datatargetId");

this.datatargetSubscription = datatargetService.get(id).subscribe((dataTarget: Datatarget) => {
this.datatarget = dataTarget;
this.datatargetType = dataTarget.type;

const component = datatargetTypesService.getDetailComponent(this.datatargetType);

this.loadComponent(component);
});
}

loadComponent(componentType: Type<any>) {
const viewContainerRef = this.adHost.viewContainerRef;
Expand All @@ -33,18 +47,7 @@ export class DatatargetDetailComponent implements OnInit, OnDestroy {
viewContainerRef.createComponent<DatatargetDetail>(componentType);
}

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() {
this.datatargetSubscription?.unsubscribe();
}

ngOnDestroy() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div class="mat-elevation-z8 datatarget-log-container">
<div class="loading-shade" *ngIf="isLoadingResults">
<mat-spinner></mat-spinner>
</div>

<table mat-table [dataSource]="dataSource" matSort matSortActive="createdAt" matSortDirection="desc">
<!-- Timestamp column -->
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ "GATEWAY.STATS-TIMESTAMP" | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.createdAt | date:'dd-MM-yyyy - HH:mm:ss' }}</td>
</ng-container>

<!-- Type column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ "DATATARGET-TABLE.TYPE" | translate }}</th>
<td mat-cell *matCellDef="let element">
<fa-icon *ngIf="element.type === 'OK'" [icon]="faCheckCircle" class="fa-ok"></fa-icon>
<fa-icon *ngIf="element.type === 'ERROR'" [icon]="faExclamationTriangle" class="fa-error"></fa-icon>
{{ ('DATATARGET.RESPONSE_TYPE.' + element.type) | translate }}
</td>
</ng-container>

<!-- Message column -->
<ng-container matColumnDef="message">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ "GEN.MESSAGE" | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.statusCode }} {{ element.message }}</td>
</ng-container>

<!-- Device column -->
<ng-container matColumnDef="device">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ "GEN.DEVICE" | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.iotDevice?.name }}</td>
</ng-container>

<!-- Actually showing the data -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>

<!-- Pagination -->
<mat-paginator [pageSizeOptions]="pageSizeOptions" [pageSize]="pageSize" showFirstLastButtons>
</mat-paginator>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:host {
.datatarget-log-container {
margin: 2em;
}
.fa-ok {
color: green !important;
}
.fa-error {
color: red !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DatatargetLogComponent } from './datatarget-log.component';

describe('DatatargetLogComponent', () => {
let component: DatatargetLogComponent;
let fixture: ComponentFixture<DatatargetLogComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DatatargetLogComponent]
})
.compileComponents();

fixture = TestBed.createComponent(DatatargetLogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AfterViewInit, Component, OnDestroy, ViewChild } from "@angular/core";
import { MatPaginator } from "@angular/material/paginator";
import { MatSort } from "@angular/material/sort";
import { MatTableDataSource } from "@angular/material/table";
import { ActivatedRoute } from "@angular/router";
import { environment } from "@environments/environment";
import { faCheckCircle, faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { DefaultPageSizeOptions } from "@shared/constants/page.constants";
import { Subscription } from "rxjs";
import { DatatargetLog } from "./datatarget-log.model";
import { DatatargetLogService } from "./datatarget-log.service";

@Component({
selector: "app-datatarget-log",
templateUrl: "./datatarget-log.component.html",
styleUrl: "./datatarget-log.component.scss",
})
export class DatatargetLogComponent implements OnDestroy, AfterViewInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

displayedColumns: string[] = ["createdAt", "type", "message", "device"];
pageSizeOptions = DefaultPageSizeOptions;
pageSize = environment.tablePageSize;
faExclamationTriangle = faExclamationTriangle;
faCheckCircle = faCheckCircle;

dataSource = new MatTableDataSource<DatatargetLog>();
isLoadingResults = true;

private datatargetLogSubscription: Subscription;

constructor(datatargetLogService: DatatargetLogService, route: ActivatedRoute) {
const id: number = +route.parent.snapshot.paramMap.get("datatargetId");

this.datatargetLogSubscription = datatargetLogService.get(id).subscribe(logs => {
this.dataSource = new MatTableDataSource<DatatargetLog>(logs);
this.setViewChildren();
this.isLoadingResults = false;
});
}

ngAfterViewInit(): void {
this.setViewChildren();
}

ngOnDestroy(): void {
this.datatargetLogSubscription?.unsubscribe();
}

private setViewChildren = () => {
if (this.dataSource) {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IotDevice } from "@applications/iot-devices/iot-device.model";

export class DatatargetLog {
createdAt: Date;

type: string;
message: string;
statusCode?: number;
iotDevice?: IotDevice;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable } from "@angular/core";
import { RestService } from "@shared/services/rest.service";
import { Observable } from "rxjs";
import { DatatargetLog } from "./datatarget-log.model";

const baseUrl = "datatarget-log";

@Injectable({
providedIn: "root",
})
export class DatatargetLogService {
constructor(private restService: RestService) {}

get(id: number): Observable<DatatargetLog[]> {
return this.restService.get(baseUrl, null, id);
}
}
Loading

0 comments on commit a5ab360

Please sign in to comment.