You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2026/06/13 10:53:54 UTC

(streampipes) branch improve-home-screen-loading-behaviour created (now d454653f92)

This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a change to branch improve-home-screen-loading-behaviour
in repository https://gitbox.apache.org/repos/asf/streampipes.git


      at d454653f92 Avoid duplicated fetching of login settings

This branch includes the following new commits:

     new 482d898ee0 fix: Improve loading behaviour of home screen
     new d454653f92 Avoid duplicated fetching of login settings

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(streampipes) 01/02: fix: Improve loading behaviour of home screen

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch improve-home-screen-loading-behaviour
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 482d898ee0a8889899cdaae301852f4cb9b04310
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sat Jun 13 12:40:49 2026 +0200

    fix: Improve loading behaviour of home screen
---
 ui/deployment/modules.yml                          |   8 +-
 ui/src/app/home/components/status.component.ts     |   2 +-
 .../welcome-tour/welcome-tour.component.html       |  45 +++++--
 .../welcome-tour/welcome-tour.component.scss       |  10 ++
 .../dialog/welcome-tour/welcome-tour.component.ts  | 127 +++++++++++++++++-
 ui/src/app/home/home.component.ts                  | 148 +++------------------
 6 files changed, 193 insertions(+), 147 deletions(-)

diff --git a/ui/deployment/modules.yml b/ui/deployment/modules.yml
index f8a7b05de1..429c018463 100644
--- a/ui/deployment/modules.yml
+++ b/ui/deployment/modules.yml
@@ -59,7 +59,7 @@ spConnect:
         createLinks: "['connect', 'catalog']"
         title: 'Adapters'
         createTitle: 'New adapter'
-        dataFns: '[this.adapterService.getAdapters()]'
+        dataFns: '[this.adapterService.getAdapterSummary()]'
         viewRoles: '[UserPrivilege.PRIVILEGE_READ_ADAPTER, UserPrivilege.PRIVILEGE_WRITE_ADAPTER]'
         createRoles: '[UserPrivilege.PRIVILEGE_WRITE_ADAPTER]'
     category: 'management'
@@ -78,7 +78,7 @@ spPipelines:
         createLinks: "['pipelines', 'create']"
         title: 'Pipelines'
         createTitle: 'New pipeline'
-        dataFns: '[this.pipelineService.getPipelines()]'
+        dataFns: '[this.pipelineService.getPipelineSummary()]'
         viewRoles: '[UserPrivilege.PRIVILEGE_READ_PIPELINE, UserPrivilege.PRIVILEGE_WRITE_PIPELINE]'
         createRoles: '[UserPrivilege.PRIVILEGE_WRITE_PIPELINE]'
     category: 'analytics'
@@ -107,7 +107,7 @@ spDashboard:
         createLinks: "['dashboard']"
         title: 'Dashboards'
         createTitle: 'New dashboard'
-        dataFns: '[this.dashboardService.getDashboards()]'
+        dataFns: '[this.dashboardService.getDashboardSummary()]'
         viewRoles: '[UserPrivilege.PRIVILEGE_READ_DASHBOARD, UserPrivilege.PRIVILEGE_WRITE_DASHBOARD]'
         createRoles: '[UserPrivilege.PRIVILEGE_WRITE_DASHBOARD]'
     category: 'visualization'
@@ -127,7 +127,7 @@ spChart:
         createLinks: "['chart']"
         title: 'Charts'
         createTitle: 'New chart'
-        dataFns: '[this.chartService.getAllCharts()]'
+        dataFns: '[this.chartService.getChartSummary()]'
         viewRoles: '[UserPrivilege.PRIVILEGE_READ_DATA_EXPLORER_VIEW, UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW]'
         createRoles: '[UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW]'
     category: 'visualization'
diff --git a/ui/src/app/home/components/status.component.ts b/ui/src/app/home/components/status.component.ts
index 58872ec0cf..5143929bd0 100644
--- a/ui/src/app/home/components/status.component.ts
+++ b/ui/src/app/home/components/status.component.ts
@@ -55,7 +55,7 @@ export class StatusComponent implements OnInit {
         zip(this.statusBox.dataFns).subscribe(res => {
             let totalLength = 0;
             res.forEach(response => {
-                totalLength += response.length;
+                totalLength += response.totalCount;
             });
 
             this.resourceCount = totalLength;
diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html
index 150983d1a9..90c028bd2c 100644
--- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html
+++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html
@@ -24,12 +24,6 @@
                 ><h3>Thank you for using {{ appConstants.APP_NAME }}.</h3></b
             >
         </div>
-        <div>
-            <img
-                src="assets/img/sp-pipeline-white-small.png"
-                style="margin-left: auto; margin-right: auto; display: block"
-            />
-        </div>
         <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column">
             <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column">
                 <b
@@ -40,15 +34,38 @@
                     If you are using {{ appConstants.APP_NAME }} for the first
                     time, we recommend taking the interactive tour!
                 </p>
+                @if (loadingTutorialResources) {
+                    <div
+                        class="tutorial-status"
+                        fxLayout="row"
+                        fxLayoutAlign="center center"
+                        fxLayoutGap="10px"
+                    >
+                        <mat-spinner [diameter]="20"></mat-spinner>
+                        <span>Checking tutorial pipeline elements...</span>
+                    </div>
+                }
+                @if (showMissingElementsHint) {
+                    <sp-alert-banner
+                        type="info"
+                        title="Missing Elements for Tutorial"
+                        description="The tutorial needs additional pipeline elements.
+                      Install the missing extensions (Settings -> Extensions) and reopen the tour."
+                    >
+                    </sp-alert-banner>
+                }
             </div>
-            <button
-                mat-button
-                mat-flat-button
-                color="accent"
-                (click)="close(true)"
-            >
-                Start Tour
-            </button>
+            @if (!loadingTutorialResources && !showMissingElementsHint) {
+                <button
+                    mat-button
+                    mat-flat-button
+                    color="accent"
+                    [disabled]="loadingTutorialResources"
+                    (click)="startTutorial()"
+                >
+                    Start Tour
+                </button>
+            }
         </div>
     </div>
     <mat-divider></mat-divider>
diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss
index c6405f3d58..a06a4f52eb 100644
--- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss
+++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss
@@ -38,3 +38,13 @@
         border-color: transparent;
     }
 }
+
+.tutorial-status {
+    margin-top: 0.5rem;
+}
+
+.tutorial-hint {
+    margin: 0.5rem 0 0;
+    text-align: center;
+    max-width: 28rem;
+}
diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts
index 60fe452a98..8e57d2e7f4 100644
--- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts
+++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts
@@ -16,11 +16,23 @@
  *
  */
 
-import { DialogRef } from '@streampipes/shared-ui';
+import {
+    DialogRef,
+    DialogService,
+    PanelType,
+    SpAlertBannerComponent,
+} from '@streampipes/shared-ui';
 import { Component, Input, OnInit, inject } from '@angular/core';
 import { AppConstants } from '../../../services/app.constants';
 import { AuthService } from '../../../services/auth.service';
-import { UserAccount, UserInfo } from '@streampipes/platform-services';
+import {
+    AdapterDescription,
+    AdapterService,
+    NamedStreamPipesEntity,
+    PipelineElementService,
+    UserAccount,
+    UserInfo,
+} from '@streampipes/platform-services';
 import { ProfileService } from '../../../profile/profile.service';
 import {
     FlexDirective,
@@ -29,6 +41,10 @@ import {
 } from '@ngbracket/ngx-layout/flex';
 import { MatButton } from '@angular/material/button';
 import { MatDivider } from '@angular/material/divider';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
+import { MissingElementsForTutorialComponent } from '../../../editor/dialog/missing-elements-for-tutorial/missing-elements-for-tutorial.component';
+import { forkJoin } from 'rxjs';
+import { LayoutGapDirective } from '@ngbracket/ngx-layout';
 
 @Component({
     selector: 'sp-welcome-tour',
@@ -40,18 +56,36 @@ import { MatDivider } from '@angular/material/divider';
         LayoutAlignDirective,
         MatButton,
         MatDivider,
+        MatProgressSpinner,
+        LayoutGapDirective,
+        SpAlertBannerComponent,
     ],
 })
 export class WelcomeTourComponent implements OnInit {
+    private static readonly REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID =
+        'org.apache.streampipes.connect.iiot.adapters.simulator.machine';
+    private static readonly REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID =
+        'org.apache.streampipes.processors.filters.jvm.numericalfilter';
+    private static readonly REQUIRED_SINK_FOR_TUTORIAL_APP_ID =
+        'org.apache.streampipes.sinks.internal.jvm.datalake';
+
     private authService = inject(AuthService);
     private dialogRef = inject<DialogRef<WelcomeTourComponent>>(DialogRef);
     private profileService = inject(ProfileService);
+    private dialogService = inject(DialogService);
+    private adapterService = inject(AdapterService);
+    private pipelineElementService = inject(PipelineElementService);
     appConstants = inject(AppConstants);
 
     @Input()
     userInfo: UserInfo;
 
     currentUser: UserAccount;
+    availablePipelineElements: NamedStreamPipesEntity[] = [];
+    availableAdapters: AdapterDescription[] = [];
+    missingElementsForTutorial = [];
+    tutorialAvailable = false;
+    loadingTutorialResources = true;
 
     ngOnInit(): void {
         this.profileService
@@ -59,6 +93,24 @@ export class WelcomeTourComponent implements OnInit {
             .subscribe(data => {
                 this.currentUser = data;
             });
+
+        forkJoin([
+            this.adapterService.getAdapterDescriptions(),
+            this.pipelineElementService.getDataStreams(),
+            this.pipelineElementService.getDataProcessors(),
+            this.pipelineElementService.getDataSinks(),
+        ]).subscribe(res => {
+            this.availableAdapters = res[0];
+            this.availablePipelineElements = []
+                .concat(...res[1])
+                .concat(...res[2])
+                .concat(...res[3]);
+            this.missingElementsForTutorial =
+                this.getMissingElementsForTutorial();
+            this.tutorialAvailable =
+                this.missingElementsForTutorial.length === 0;
+            this.loadingTutorialResources = false;
+        });
     }
 
     hideTourForever() {
@@ -71,7 +123,78 @@ export class WelcomeTourComponent implements OnInit {
             });
     }
 
+    startTutorial() {
+        if (this.loadingTutorialResources) {
+            return;
+        }
+
+        if (this.tutorialAvailable) {
+            this.close(true);
+        } else {
+            this.dialogService.open(MissingElementsForTutorialComponent, {
+                panelType: PanelType.STANDARD_PANEL,
+                title: 'Tutorial requires pipeline elements',
+                data: {
+                    missingElementsForTutorial: this.missingElementsForTutorial,
+                },
+            });
+        }
+    }
+
     close(startTutorial = false) {
         this.dialogRef.close(startTutorial);
     }
+
+    get showMissingElementsHint() {
+        return !this.loadingTutorialResources && !this.tutorialAvailable;
+    }
+
+    private getMissingElementsForTutorial() {
+        const missingElements = [];
+
+        if (
+            !this.requiredPeForTourPresent(
+                this.availableAdapters,
+                WelcomeTourComponent.REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID,
+            )
+        ) {
+            missingElements.push({
+                name: 'Machine Data Simulator',
+                appId: WelcomeTourComponent.REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID,
+            });
+        }
+
+        if (
+            !this.requiredPeForTourPresent(
+                this.availablePipelineElements,
+                WelcomeTourComponent.REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID,
+            )
+        ) {
+            missingElements.push({
+                name: 'Numerical Filter',
+                appId: WelcomeTourComponent.REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID,
+            });
+        }
+
+        if (
+            !this.requiredPeForTourPresent(
+                this.availablePipelineElements,
+                WelcomeTourComponent.REQUIRED_SINK_FOR_TUTORIAL_APP_ID,
+            )
+        ) {
+            missingElements.push({
+                name: 'Dashboard Sink',
+                appId: WelcomeTourComponent.REQUIRED_SINK_FOR_TUTORIAL_APP_ID,
+            });
+        }
+
+        return missingElements;
+    }
+
+    private requiredPeForTourPresent(
+        list: Array<{ appId: string }>,
+        appId: string,
+    ) {
+        return list?.some(el => el.appId === appId);
+    }
 }
diff --git a/ui/src/app/home/home.component.ts b/ui/src/app/home/home.component.ts
index 5a625422db..a73f4303aa 100644
--- a/ui/src/app/home/home.component.ts
+++ b/ui/src/app/home/home.component.ts
@@ -31,12 +31,9 @@ import {
     SplitSectionComponent,
 } from '@streampipes/shared-ui';
 import { UserRole } from '../core/auth/user-role.enum';
-import { MissingElementsForTutorialComponent } from '../editor/dialog/missing-elements-for-tutorial/missing-elements-for-tutorial.component';
 import { WelcomeTourComponent } from './dialog/welcome-tour/welcome-tour.component';
 import { ShepherdService } from '../services/tour/shepherd.service';
 import {
-    AdapterDescription,
-    AdapterService,
     AssetConstants,
     AssetLinkType,
     AssetManagementService,
@@ -44,12 +41,10 @@ import {
     GenericStorageService,
     LocationConfig,
     LocationConfigService,
-    NamedStreamPipesEntity,
-    PipelineElementService,
     SpAssetModel,
     UserInfo,
 } from '@streampipes/platform-services';
-import { forkJoin, Subscription, zip } from 'rxjs';
+import { forkJoin, Subscription } from 'rxjs';
 import { StatusBox } from './models/home.model';
 import {
     FlexDirective,
@@ -92,9 +87,6 @@ export class HomeComponent implements OnInit, OnDestroy {
     serviceLinks = [];
     showStatus = false;
 
-    availablePipelineElements: NamedStreamPipesEntity[] = [];
-    availableAdapters: AdapterDescription[] = [];
-
     statusBoxes: StatusBox[] = [];
     locationConfig: LocationConfig;
     assets: SpAssetModel[] = [];
@@ -102,14 +94,6 @@ export class HomeComponent implements OnInit, OnDestroy {
     sites: Record<string, AssetSiteDesc> = {};
     assetLinkTypes: Record<string, AssetLinkType> = {};
 
-    requiredAdapterForTutorialAppId: any =
-        'org.apache.streampipes.connect.iiot.adapters.simulator.machine';
-    requiredProcessorForTutorialAppId: any =
-        'org.apache.streampipes.processors.filters.jvm.numericalfilter';
-    requiredSinkForTutorialAppId: any =
-        'org.apache.streampipes.sinks.internal.jvm.datalake';
-    missingElementsForTutorial: any = [];
-
     isTutorialOpen = false;
     currentUser: UserInfo;
     selectedView = signal<string>('table');
@@ -122,8 +106,6 @@ export class HomeComponent implements OnInit, OnDestroy {
     private breadcrumbService = inject(SpBreadcrumbService);
     private dialogService = inject(DialogService);
     private shepherdService = inject(ShepherdService);
-    private pipelineElementService = inject(PipelineElementService);
-    private adapterService = inject(AdapterService);
     private genericStorageService = inject(GenericStorageService);
     private locationService = inject(LocationConfigService);
     private assetService = inject(AssetManagementService);
@@ -175,8 +157,10 @@ export class HomeComponent implements OnInit, OnDestroy {
             this.contentLoaded = true;
             this.showStatus = true;
         });
-        if (isAdmin) {
-            this.loadResources();
+        console.log(isAdmin);
+        console.log(this.currentUser.showTutorial);
+        if (isAdmin && this.currentUser.showTutorial) {
+            this.checkForTutorial();
         }
         this.breadcrumbService.updateBreadcrumb([]);
     }
@@ -199,115 +183,27 @@ export class HomeComponent implements OnInit, OnDestroy {
     }
 
     checkForTutorial() {
-        if (this.currentUser.showTutorial) {
-            if (this.requiredPipelineElementsForTourPresent()) {
-                this.isTutorialOpen = true;
-                const dialogRef = this.dialogService.open(
-                    WelcomeTourComponent,
-                    {
-                        panelType: PanelType.STANDARD_PANEL,
-                        title: 'Welcome to ' + this.appConstants.APP_NAME,
-                        data: {
-                            userInfo: this.currentUser,
-                        },
-                    },
-                );
-                dialogRef.afterClosed().subscribe(startTutorial => {
-                    if (startTutorial) {
-                        this.startTutorial();
-                    }
-                });
+        this.isTutorialOpen = true;
+        const dialogRef = this.dialogService.open(WelcomeTourComponent, {
+            panelType: PanelType.STANDARD_PANEL,
+            title: 'Welcome to ' + this.appConstants.APP_NAME,
+            data: {
+                userInfo: this.currentUser,
+            },
+        });
+        dialogRef.afterClosed().subscribe(startTutorial => {
+            if (startTutorial) {
+                this.startTutorial();
+            } else {
+                this.isTutorialOpen = false;
             }
-        }
+        });
     }
 
     startTutorial() {
-        if (this.requiredPipelineElementsForTourPresent()) {
-            this.router.navigate(['connect']).then(() => {
-                this.shepherdService.startAdapterTour();
-            });
-        } else {
-            this.missingElementsForTutorial = [];
-            if (!this.requiredAdapterForTutorialAppId()) {
-                this.missingElementsForTutorial.push({
-                    name: 'Machine Data Simulator',
-                    appId: this.requiredAdapterForTutorialAppId,
-                });
-            }
-            if (!this.requiredProcessorForTourPresent()) {
-                this.missingElementsForTutorial.push({
-                    name: 'Numerical Filter',
-                    appId: this.requiredProcessorForTutorialAppId,
-                });
-            }
-            if (!this.requiredSinkForTourPresent()) {
-                this.missingElementsForTutorial.push({
-                    name: 'Dashboard Sink',
-                    appId: this.requiredSinkForTutorialAppId,
-                });
-            }
-
-            this.dialogService.open(MissingElementsForTutorialComponent, {
-                panelType: PanelType.STANDARD_PANEL,
-                title: 'Tutorial requires pipeline elements',
-                data: {
-                    missingElementsForTutorial: this.missingElementsForTutorial,
-                },
-            });
-        }
-    }
-
-    requiredPipelineElementsForTourPresent() {
-        return (
-            this.requiredAdapterForTourPresent() &&
-            this.requiredProcessorForTourPresent() &&
-            this.requiredSinkForTourPresent()
-        );
-    }
-
-    requiredAdapterForTourPresent() {
-        return this.requiredPeForTourPresent(
-            this.availableAdapters,
-            this.requiredAdapterForTutorialAppId,
-        );
-    }
-
-    requiredProcessorForTourPresent() {
-        return this.requiredPeForTourPresent(
-            this.availablePipelineElements,
-            this.requiredProcessorForTutorialAppId,
-        );
-    }
-
-    requiredSinkForTourPresent() {
-        return this.requiredPeForTourPresent(
-            this.availablePipelineElements,
-            this.requiredSinkForTutorialAppId,
-        );
-    }
-
-    requiredPeForTourPresent(list: NamedStreamPipesEntity[], appId: string) {
-        return (
-            list &&
-            list.some(el => {
-                return el.appId === appId;
-            })
-        );
-    }
-
-    loadResources(): void {
-        zip(
-            this.adapterService.getAdapterDescriptions(),
-            this.pipelineElementService.getDataStreams(),
-            this.pipelineElementService.getDataProcessors(),
-            this.pipelineElementService.getDataSinks(),
-        ).subscribe(res => {
-            this.availableAdapters = res[0];
-            this.availablePipelineElements = this.availablePipelineElements
-                .concat(...res[1])
-                .concat(...res[2])
-                .concat(...res[3]);
-            this.checkForTutorial();
+        this.router.navigate(['connect']).then(() => {
+            this.shepherdService.startAdapterTour();
+            this.isTutorialOpen = false;
         });
     }
 


(streampipes) 02/02: Avoid duplicated fetching of login settings

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch improve-home-screen-loading-behaviour
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit d454653f926b60d35bb0b7a4f333c2e2e38cd8c0
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sat Jun 13 12:53:28 2026 +0200

    Avoid duplicated fetching of login settings
---
 .../registration-allowed.can-activate.guard.ts     |  6 +--
 .../restore-password-allowed.can-activate.guard.ts |  8 ++--
 .../guards/terms.can-activate-children.guard.ts    |  6 +--
 ui/src/app/help/help.component.ts                  |  6 +--
 .../login/components/base-login-page.directive.ts  |  4 +-
 .../services/login-settings.service.ts}            | 50 ++++++++++------------
 ui/src/app/login/services/login.service.ts         | 16 +------
 7 files changed, 40 insertions(+), 56 deletions(-)

diff --git a/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts b/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts
index ccccc288e5..2b7f1e86ae 100644
--- a/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts
+++ b/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts
@@ -23,14 +23,14 @@ import {
     RouterStateSnapshot,
     UrlTree,
 } from '@angular/router';
-import { LoginService } from '../../../login/services/login.service';
+import { LoginSettingsService } from '../../../login/services/login-settings.service';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 
 @Injectable({ providedIn: 'root' })
 export class RegistrationAllowedCanActivateGuard {
     private router = inject(Router);
-    private loginService = inject(LoginService);
+    private loginSettingsService = inject(LoginSettingsService);
 
     canActivate(
         _route: ActivatedRouteSnapshot,
@@ -40,7 +40,7 @@ export class RegistrationAllowedCanActivateGuard {
         | Promise<boolean | UrlTree>
         | boolean
         | UrlTree {
-        return this.loginService.fetchLoginSettings().pipe(
+        return this.loginSettingsService.getSettings().pipe(
             map(config => {
                 return config.allowSelfRegistration
                     ? true
diff --git a/ui/src/app/core/setup/guards/restore-password-allowed.can-activate.guard.ts b/ui/src/app/core/setup/guards/restore-password-allowed.can-activate.guard.ts
index e7f51df072..66c1060aef 100644
--- a/ui/src/app/core/setup/guards/restore-password-allowed.can-activate.guard.ts
+++ b/ui/src/app/core/setup/guards/restore-password-allowed.can-activate.guard.ts
@@ -23,14 +23,14 @@ import {
     RouterStateSnapshot,
     UrlTree,
 } from '@angular/router';
-import { LoginService } from '../../../login/services/login.service';
+import { LoginSettingsService } from '../../../login/services/login-settings.service';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 
 @Injectable({ providedIn: 'root' })
 export class RestorePasswordAllowedCanActivateGuard {
     private router = inject(Router);
-    private loginService = inject(LoginService);
+    private loginSettingsService = inject(LoginSettingsService);
 
     canActivate(
         _route: ActivatedRouteSnapshot,
@@ -40,8 +40,8 @@ export class RestorePasswordAllowedCanActivateGuard {
         | Promise<boolean | UrlTree>
         | boolean
         | UrlTree {
-        return this.loginService
-            .fetchLoginSettings()
+        return this.loginSettingsService
+            .getSettings()
             .pipe(
                 map(config =>
                     config.allowPasswordRecovery
diff --git a/ui/src/app/core/setup/guards/terms.can-activate-children.guard.ts b/ui/src/app/core/setup/guards/terms.can-activate-children.guard.ts
index 7eaf577d50..deb2e4e2f0 100644
--- a/ui/src/app/core/setup/guards/terms.can-activate-children.guard.ts
+++ b/ui/src/app/core/setup/guards/terms.can-activate-children.guard.ts
@@ -26,7 +26,7 @@ import {
     RouterStateSnapshot,
 } from '@angular/router';
 import { CurrentUserService } from '@streampipes/shared-ui';
-import { LoginService } from '../../../login/services/login.service';
+import { LoginSettingsService } from '../../../login/services/login-settings.service';
 import { of, take } from 'rxjs';
 import { catchError, map } from 'rxjs/operators';
 
@@ -37,7 +37,7 @@ export class TermsCanActivateChildrenGuard implements CanActivateChild {
         state: RouterStateSnapshot,
     ): MaybeAsync<GuardResult> {
         const currentUser = this.currentUserService.getCurrentUser();
-        return this.loginService.fetchLoginSettings().pipe(
+        return this.loginSettingsService.getSettings().pipe(
             take(1),
             map(settings => {
                 const needsAck =
@@ -56,7 +56,7 @@ export class TermsCanActivateChildrenGuard implements CanActivateChild {
     }
 
     private currentUserService = inject(CurrentUserService);
-    private loginService = inject(LoginService);
+    private loginSettingsService = inject(LoginSettingsService);
 
     private router = inject(Router);
 }
diff --git a/ui/src/app/help/help.component.ts b/ui/src/app/help/help.component.ts
index f85d009b64..214e696ad6 100644
--- a/ui/src/app/help/help.component.ts
+++ b/ui/src/app/help/help.component.ts
@@ -27,7 +27,7 @@ import {
     LayoutDirective,
 } from '@ngbracket/ngx-layout/flex';
 import { MatTab, MatTabGroup } from '@angular/material/tabs';
-import { LoginService } from '../login/services/login.service';
+import { LoginSettingsService } from '../login/services/login-settings.service';
 import { InfoTabComponent } from './components/info/info.component';
 import { DocumentationTabComponent } from './components/documentation/documentation.component';
 import { ShortcutsTabComponent } from './components/shortcuts/shortcuts.component';
@@ -52,7 +52,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
 })
 export class HelpComponent implements OnInit {
     private breadcrumbService = inject(SpBreadcrumbService);
-    private loginService = inject(LoginService);
+    private loginSettingsService = inject(LoginSettingsService);
     private translateService = inject(TranslateService);
 
     selectedIndex = 0;
@@ -67,7 +67,7 @@ export class HelpComponent implements OnInit {
         this.breadcrumbService.updateBreadcrumb([
             { label: this.translateService.instant('Help') },
         ]);
-        this.loginService.fetchLoginSettings().subscribe(res => {
+        this.loginSettingsService.getSettings().subscribe(res => {
             this.documentationLink = res.linkSettings?.documentationUrl || '';
             this.showDocumentationTab =
                 !!res.linkSettings?.showDocumentationLinkInProfileMenu &&
diff --git a/ui/src/app/login/components/base-login-page.directive.ts b/ui/src/app/login/components/base-login-page.directive.ts
index ef15cb926f..87228dbc98 100644
--- a/ui/src/app/login/components/base-login-page.directive.ts
+++ b/ui/src/app/login/components/base-login-page.directive.ts
@@ -18,6 +18,7 @@
 
 import { Directive, inject, OnInit } from '@angular/core';
 import { LoginService } from '../services/login.service';
+import { LoginSettingsService } from '../services/login-settings.service';
 import { LoginModel } from './login/login.model';
 
 @Directive()
@@ -26,9 +27,10 @@ export abstract class BaseLoginPageDirective implements OnInit {
     protected configReady = false;
 
     protected loginService = inject(LoginService);
+    protected loginSettingsService = inject(LoginSettingsService);
 
     ngOnInit(): void {
-        this.loginService.fetchLoginSettings().subscribe(result => {
+        this.loginSettingsService.getSettings().subscribe(result => {
             this.loginSettings = result;
             this.configReady = true;
             this.onSettingsAvailable();
diff --git a/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts b/ui/src/app/login/services/login-settings.service.ts
similarity index 51%
copy from ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts
copy to ui/src/app/login/services/login-settings.service.ts
index ccccc288e5..347a74399b 100644
--- a/ui/src/app/core/setup/guards/registration-allowed.can-activate.guard.ts
+++ b/ui/src/app/login/services/login-settings.service.ts
@@ -17,35 +17,31 @@
  */
 
 import { Injectable, inject } from '@angular/core';
-import {
-    ActivatedRouteSnapshot,
-    Router,
-    RouterStateSnapshot,
-    UrlTree,
-} from '@angular/router';
-import { LoginService } from '../../../login/services/login.service';
-import { Observable } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { HttpClient } from '@angular/common/http';
+import { PlatformServicesCommons } from '@streampipes/platform-services';
+import { Observable, shareReplay } from 'rxjs';
+import { LoginModel } from '../components/login/login.model';
 
 @Injectable({ providedIn: 'root' })
-export class RegistrationAllowedCanActivateGuard {
-    private router = inject(Router);
-    private loginService = inject(LoginService);
+export class LoginSettingsService {
+    private http = inject(HttpClient);
+    private platformServicesCommons = inject(PlatformServicesCommons);
 
-    canActivate(
-        _route: ActivatedRouteSnapshot,
-        _state: RouterStateSnapshot,
-    ):
-        | Observable<boolean | UrlTree>
-        | Promise<boolean | UrlTree>
-        | boolean
-        | UrlTree {
-        return this.loginService.fetchLoginSettings().pipe(
-            map(config => {
-                return config.allowSelfRegistration
-                    ? true
-                    : this.router.parseUrl('register');
-            }),
-        );
+    private settings$?: Observable<LoginModel>;
+
+    getSettings(): Observable<LoginModel> {
+        if (!this.settings$) {
+            this.settings$ = this.http
+                .get<LoginModel>(
+                    `${this.platformServicesCommons.apiBasePath}/auth/settings`,
+                )
+                .pipe(shareReplay(1));
+        }
+
+        return this.settings$;
+    }
+
+    invalidateCache(): void {
+        this.settings$ = undefined;
     }
 }
diff --git a/ui/src/app/login/services/login.service.ts b/ui/src/app/login/services/login.service.ts
index d5b17f1df1..95a886acff 100644
--- a/ui/src/app/login/services/login.service.ts
+++ b/ui/src/app/login/services/login.service.ts
@@ -19,8 +19,7 @@
 import { Injectable, inject } from '@angular/core';
 import { HttpClient, HttpContext } from '@angular/common/http';
 import { PlatformServicesCommons } from '@streampipes/platform-services';
-import { Observable, shareReplay } from 'rxjs';
-import { LoginModel } from '../components/login/login.model';
+import { Observable } from 'rxjs';
 import { RegistrationModel } from '../components/register/registration.model';
 import { NGX_LOADING_BAR_IGNORED } from '@ngx-loading-bar/http-client';
 
@@ -29,19 +28,6 @@ export class LoginService {
     private http = inject(HttpClient);
     private platformServicesCommons = inject(PlatformServicesCommons);
 
-    private settings$?: Observable<LoginModel>;
-
-    fetchLoginSettings(): Observable<LoginModel> {
-        if (!this.settings$) {
-            this.settings$ = this.http
-                .get<LoginModel>(
-                    `${this.platformServicesCommons.apiBasePath}/auth/settings`,
-                )
-                .pipe(shareReplay({ bufferSize: 1, refCount: true }));
-        }
-        return this.settings$;
-    }
-
     login(credentials): Observable<any> {
         return this.http.post(
             this.platformServicesCommons.apiBasePath + '/auth/login',