This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch 3725-add-dashboard-kiosk-mode in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 31c607006ab74afd1cd3e03de416f9c2497b083a Author: Dominik Riemer <[email protected]> AuthorDate: Tue Aug 12 11:13:54 2025 +0200 Add auto-refresh for kiosk mode --- .../model/dashboard/CompositeDashboardModel.java | 10 +++++ .../impl/dashboard/DataLakeDashboardResource.java | 24 +++++++++-- .../src/lib/apis/dashboard.service.ts | 12 +++++- .../src/lib/model/dashboard/dashboard.model.ts | 1 + .../components/kiosk/dashboard-kiosk.component.ts | 46 +++++++++++++++++++--- .../grid-view/dashboard-grid-view.component.html | 4 +- .../slide-view/dashboard-slide-view.component.html | 4 +- .../components/panel/dashboard-panel.component.ts | 15 ++++--- .../data-explorer-chart-container.component.ts | 2 +- .../charts/base/echarts-widget.component.ts | 3 +- .../data-explorer-chart-view.component.html | 4 +- 11 files changed, 100 insertions(+), 25 deletions(-) diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/dashboard/CompositeDashboardModel.java b/streampipes-model/src/main/java/org/apache/streampipes/model/dashboard/CompositeDashboardModel.java index cb06527e42..cd14784aaa 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/dashboard/CompositeDashboardModel.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/dashboard/CompositeDashboardModel.java @@ -22,8 +22,18 @@ import org.apache.streampipes.model.datalake.DataExplorerWidgetModel; import org.apache.streampipes.model.datalake.DataLakeMeasure; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public record CompositeDashboardModel(DashboardModel dashboard, List<DataExplorerWidgetModel> widgets, List<DataLakeMeasure> dataLakeMeasures) { + + public String getRevisionHash() { + + return Stream.concat( + Stream.of(dashboard.getRev()), + widgets.stream().map(DashboardEntity::getRev) + ).collect(Collectors.joining("|")); + } } diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java index f97b7083a1..0fcb6e2162 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java @@ -20,11 +20,12 @@ package org.apache.streampipes.rest.impl.dashboard; import org.apache.streampipes.model.client.user.DefaultPrivilege; -import org.apache.streampipes.model.dashboard.CompositeDashboardModel; import org.apache.streampipes.model.dashboard.DashboardModel; import org.apache.streampipes.resource.management.DataExplorerResourceManager; import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PostFilter; @@ -35,10 +36,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Objects; @RestController @RequestMapping("/api/v3/datalake/dashboard") @@ -59,8 +62,23 @@ public class DataLakeDashboardResource extends AbstractAuthGuardedRestResource { @GetMapping(path = "/{dashboardId}/composite", produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("this.hasReadAuthority() and hasPermission(#dashboardId, 'READ')") - public CompositeDashboardModel getCompositeDashboardModel(@PathVariable("dashboardId") String dashboardId) { - return getResourceManager().getCompositeDashboard(dashboardId); + public ResponseEntity<?> getCompositeDashboardModel(@PathVariable("dashboardId") String dashboardId, + @RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) { + var dashboard = getResourceManager().getCompositeDashboard(dashboardId); + var currentEtag = "\"" + dashboard.getRevisionHash() + "\""; + if (Objects.nonNull(ifNoneMatch)) { + if (currentEtag.equals(ifNoneMatch)) { + return ResponseEntity + .status(HttpStatus.NOT_MODIFIED) + .eTag(currentEtag) + .build(); + } + } + return ResponseEntity + .ok() + .eTag(currentEtag) + .cacheControl(CacheControl.noCache()) + .body(dashboard); } @PutMapping(path = "/{dashboardId}", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/dashboard.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/dashboard.service.ts index 4a40b03218..9539fe2831 100644 --- a/ui/projects/streampipes/platform-services/src/lib/apis/dashboard.service.ts +++ b/ui/projects/streampipes/platform-services/src/lib/apis/dashboard.service.ts @@ -16,7 +16,7 @@ * */ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { SharedDatalakeRestService } from './shared-dashboard.service'; @@ -42,9 +42,17 @@ export class DashboardService { return this.http.get<Dashboard>(`${this.dashboardUrl}/${dashboardId}`); } - getCompositeDashboard(dashboardId: string): Observable<CompositeDashboard> { + getCompositeDashboard( + dashboardId: string, + eTag = undefined, + ): Observable<HttpResponse<any>> { + const headers = eTag ? { 'If-None-Match': eTag } : {}; return this.http.get<CompositeDashboard>( `${this.dashboardUrl}/${dashboardId}/composite`, + { + headers, + observe: 'response', + }, ); } diff --git a/ui/projects/streampipes/platform-services/src/lib/model/dashboard/dashboard.model.ts b/ui/projects/streampipes/platform-services/src/lib/model/dashboard/dashboard.model.ts index 927d1a1946..79bd32d7b6 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/dashboard/dashboard.model.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/dashboard/dashboard.model.ts @@ -59,4 +59,5 @@ export interface CompositeDashboard { dashboard: Dashboard; dataLakeMeasures: DataLakeMeasure[]; widgets: DataExplorerWidgetModel[]; + revisionHash: string; } diff --git a/ui/src/app/dashboard-kiosk/components/kiosk/dashboard-kiosk.component.ts b/ui/src/app/dashboard-kiosk/components/kiosk/dashboard-kiosk.component.ts index 783c6c9090..71b90b579e 100644 --- a/ui/src/app/dashboard-kiosk/components/kiosk/dashboard-kiosk.component.ts +++ b/ui/src/app/dashboard-kiosk/components/kiosk/dashboard-kiosk.component.ts @@ -1,5 +1,6 @@ import { Component, inject, OnDestroy, OnInit } from '@angular/core'; import { + CompositeDashboard, Dashboard, DashboardService, DataExplorerWidgetModel, @@ -24,20 +25,31 @@ export class DashboardKioskComponent implements OnInit, OnDestroy { dashboard: Dashboard; widgets: DataExplorerWidgetModel[] = []; refresh$: Subscription; + eTag: string; ngOnInit() { const dashboardId = this.route.snapshot.params.dashboardId; this.dashboardService .getCompositeDashboard(dashboardId) - .subscribe(cd => { - this.dashboard = cd.dashboard; - this.widgets = cd.widgets; - if (this.dashboard.dashboardLiveSettings.refreshModeActive) { - this.createQuerySubscription(); + .subscribe(res => { + if (res.ok) { + const cd = res.body; + const eTag = res.headers.get('ETag'); + this.initDashboard(cd, eTag); } }); } + initDashboard(cd: CompositeDashboard, eTag: string): void { + this.dashboard = cd.dashboard; + this.widgets = cd.widgets; + this.eTag = eTag; + if (this.dashboard.dashboardLiveSettings.refreshModeActive) { + this.createQuerySubscription(); + this.createRefreshListener(); + } + } + createQuerySubscription() { this.refresh$ = timer( 0, @@ -58,6 +70,30 @@ export class DashboardKioskComponent implements OnInit, OnDestroy { .subscribe(); } + createRefreshListener(): void { + this.dashboardService + .getCompositeDashboard(this.dashboard.elementId, this.eTag) // this should send If-None-Match + .subscribe({ + next: res => { + if (res.status === 200) { + const newEtag = res.headers.get('ETag'); + if (newEtag) { + this.eTag = newEtag; + } + this.dashboard = undefined; + this.refresh$?.unsubscribe(); + setTimeout(() => { + this.initDashboard(res.body, newEtag); + }); + } + setTimeout(() => this.createRefreshListener(), 5000); + }, + error: err => { + setTimeout(() => this.createRefreshListener(), 5000); + }, + }); + } + updateDateRange(timeSettings: TimeSettings) { let ts = undefined; if (this.dashboard.dashboardGeneralSettings.globalTimeEnabled) { diff --git a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html index 9b3bbe4170..fb34956764 100644 --- a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html +++ b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html @@ -35,7 +35,7 @@ class="widget-outer" > @if (widgetsAvailable && configuredWidgets.has(item.id)) { - <sp-data-explorer-dashboard-widget + <sp-data-explorer-chart-container [ngStyle]="{ height: gridsterItemComponent.height - 13 + 'px' }" @@ -53,7 +53,7 @@ [gridMode]="true" [widgetIndex]="i" [gridsterItemComponent]="gridsterItemComponent" - ></sp-data-explorer-dashboard-widget> + ></sp-data-explorer-chart-container> } </gridster-item> </ng-container> diff --git a/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html b/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html index 4c73a94118..1640260e3a 100644 --- a/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html +++ b/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html @@ -48,7 +48,7 @@ </div> <div fxFlex="100"> <div class="h-100 w-100 mw-100" id="slideViewOuter" fxFlex="100"> - <sp-data-explorer-dashboard-widget + <sp-data-explorer-chart-container [ngStyle]="{ height: gridsterItemComponent.height - 15 + 'px' }" @@ -71,7 +71,7 @@ currentWidget && widgetsVisible " - ></sp-data-explorer-dashboard-widget> + ></sp-data-explorer-chart-container> </div> </div> </div> diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts index f16f0de104..6c6ab6c2da 100644 --- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts +++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts @@ -209,12 +209,15 @@ export class DashboardPanelComponent getDashboard(dashboardId: string, startTime: number, endTime: number) { this.dashboardService .getCompositeDashboard(dashboardId) - .subscribe(compositeDashboard => { - this.dashboard = compositeDashboard.dashboard; - this.widgets = compositeDashboard.widgets; - this.originalDashboard = JSON.parse( - JSON.stringify(compositeDashboard.dashboard), - ); + .subscribe(resp => { + if (resp.ok) { + const compositeDashboard = resp.body; + this.dashboard = compositeDashboard.dashboard; + this.widgets = compositeDashboard.widgets; + this.originalDashboard = JSON.parse( + JSON.stringify(compositeDashboard.dashboard), + ); + } this.breadcrumbService.updateBreadcrumb( this.breadcrumbService.makeRoute( [SpDashboardRoutes.BASE], diff --git a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts index d3c1193dac..91f35ac40c 100644 --- a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts +++ b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts @@ -58,7 +58,7 @@ import { DataExplorerSharedService } from '../../services/data-explorer-shared.s import { MatMenuTrigger } from '@angular/material/menu'; @Component({ - selector: 'sp-data-explorer-dashboard-widget', + selector: 'sp-data-explorer-chart-container', templateUrl: './data-explorer-chart-container.component.html', styleUrls: ['./data-explorer-chart-container.component.scss'], standalone: false, diff --git a/ui/src/app/data-explorer-shared/components/charts/base/echarts-widget.component.ts b/ui/src/app/data-explorer-shared/components/charts/base/echarts-widget.component.ts index a3d366367b..375499beda 100644 --- a/ui/src/app/data-explorer-shared/components/charts/base/echarts-widget.component.ts +++ b/ui/src/app/data-explorer-shared/components/charts/base/echarts-widget.component.ts @@ -125,7 +125,7 @@ export class SpEchartsWidgetComponent<T extends DataExplorerWidgetModel> ), }; if (this.kioskMode) { - ['legend', 'toolbox', 'visualMap'].forEach(key => { + ['toolbox', 'visualMap'].forEach(key => { const item = this.option[key]; if (item) { (Array.isArray(item) ? item : [item]).forEach( @@ -141,7 +141,6 @@ export class SpEchartsWidgetComponent<T extends DataExplorerWidgetModel> bottom: 60, }, }); - console.log(this.option); } } else { this.showInvalidConfiguration = true; diff --git a/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.html b/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.html index ac7c19b8c4..9bb81bf9b0 100644 --- a/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.html +++ b/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.html @@ -68,7 +68,7 @@ </mat-drawer> <mat-drawer-content class="h-100 dashboard-grid"> <div #panel fxFlex="100" fxLayout="column"> - <sp-data-explorer-dashboard-widget + <sp-data-explorer-chart-container *ngIf=" dataView && gridsterItemComponent && @@ -83,7 +83,7 @@ " (startEditModeEmitter)="editDataView()" > - </sp-data-explorer-dashboard-widget> + </sp-data-explorer-chart-container> </div> </mat-drawer-content> </mat-drawer-container>
