diff --git a/projects/hslayers/assets/locales/cs.json b/projects/hslayers/assets/locales/cs.json
index c7db79892f..85aa6545c8 100644
--- a/projects/hslayers/assets/locales/cs.json
+++ b/projects/hslayers/assets/locales/cs.json
@@ -1,4 +1,12 @@
{
+ "AUTH": {
+ "signingIn": "Přihlašování ...",
+ "logoutSuccessful": "Byli jste velmi odhlášeni!",
+ "loginSuccessful": "Úspěšně přihlášen",
+ "authenticationFailed": "Chyba ověření",
+ "userInfoFailed": "Chyba při načítání uživatelských údajů",
+ "loginFailed": "Přihlášení se nezdařilo"
+ },
"ADDDATA": {
"CATALOGUE": {
"DESC": {
@@ -322,7 +330,9 @@
"base": "Podkladová mapa",
"thematic": "Tematická mapa",
"contains": "obsahuje",
- "property": "Atribut"
+ "property": "Atribut",
+ "error": "Chyba",
+ "success": "Úspěch"
},
"COMPOSITIONS": {
"addByAddress": "Přidat kompozici z adresy",
diff --git a/projects/hslayers/assets/locales/en.json b/projects/hslayers/assets/locales/en.json
index 1c9e749e10..63373a3b64 100644
--- a/projects/hslayers/assets/locales/en.json
+++ b/projects/hslayers/assets/locales/en.json
@@ -1,4 +1,14 @@
{
+ "AUTH": {
+ "signingIn": "Signing in...",
+ "logoutSuccessful": "You have been sucesfully logged out!",
+ "loginSuccessful": "Successfully logged in",
+ "authenticationFailed": "Authentication failed",
+ "userInfoFailed": "Failed to retrieve user information",
+ "loginFailed": "Login failed",
+ "Login": "Log in",
+ "Logout": "Log out"
+ },
"ADDDATA": {
"CATALOGUE": {
"DESC": {
@@ -219,8 +229,6 @@
"lineColor": "Line color",
"lineWidth": "Line width",
"loading": "Loading",
- "Login": "Log in",
- "Logout": "Log out",
"mapExtentFilterMissing": "Please enable Filter by map extent checkbox to be able to sort data by Bounding box",
"menu": "Menu",
"metadata": "Metadata",
@@ -322,7 +330,9 @@
"base": "Basemap",
"thematic": "Thematic map",
"contains": "contains",
- "property": "Property"
+ "property": "Property",
+ "error": "Error",
+ "success": "Success"
},
"COMPOSITIONS": {
"addByAddress": "Add composition by address",
diff --git a/projects/hslayers/assets/locales/sk.json b/projects/hslayers/assets/locales/sk.json
index 0347705697..855a85ae6f 100644
--- a/projects/hslayers/assets/locales/sk.json
+++ b/projects/hslayers/assets/locales/sk.json
@@ -1,4 +1,12 @@
{
+ "AUTH": {
+ "signingIn": "Prihlasovanie ...",
+ "logoutSuccessful": "Boli ste úspešne odhlásený!",
+ "loginSuccessful": "Úspešne prihlásený",
+ "authenticationFailed": "Chyba prihlásenia",
+ "userInfoFailed": "Chyba pri načítaní údajov o užívateľovi",
+ "loginFailed": "Prihlasovanie nebolo úspešné"
+ },
"ADDDATA": {
"CATALOGUE": {
"DESC": {
@@ -322,7 +330,9 @@
"base": "Podkladová mapa",
"thematic": "Tematická mapa",
"contains": "obsahuje",
- "property": "Atribút"
+ "property": "Atribút",
+ "error": "Chyba",
+ "success": "Úspech"
},
"COMPOSITIONS": {
"addByAddress": "Pridať kompozíciu z adresy",
diff --git a/projects/hslayers/common/dialog-set-permissions/set-permissions.component.ts b/projects/hslayers/common/dialog-set-permissions/set-permissions.component.ts
index 9aa2fb56ab..26ea3b61dd 100644
--- a/projects/hslayers/common/dialog-set-permissions/set-permissions.component.ts
+++ b/projects/hslayers/common/dialog-set-permissions/set-permissions.component.ts
@@ -57,7 +57,7 @@ export class HsSetPermissionsDialogComponent
ngOnInit(): void {
// Can set permission for Layman endpoint only
- this.endpoint = this.hsCommonLaymanService.layman;
+ this.endpoint = this.hsCommonLaymanService.layman();
if (!this.data.selectedRecord?.access_rights || !this.endpoint) {
this.close();
return;
@@ -70,7 +70,7 @@ export class HsSetPermissionsDialogComponent
* so it can be used for Layman access rights component
*/
parseCurrentPermissions(): void {
- const currentUser = this.endpoint.user;
+ const currentUser = this.hsCommonLaymanService.user();
let read: string[] = this.data.selectedRecord.access_rights.read;
let write: string[] = this.data.selectedRecord.access_rights.write;
if (read.includes('EVERYONE')) {
@@ -105,7 +105,7 @@ export class HsSetPermissionsDialogComponent
const layerDesc: UpsertLayerObject = {
name: this.data.selectedRecord.name,
title: this.data.selectedRecord.title,
- workspace: this.endpoint.user,
+ workspace: this.hsCommonLaymanService.user(),
access_rights: this.access_rights,
};
response = await this.hsLaymanService.makeUpsertLayerRequest(
diff --git a/projects/hslayers/common/layman/access-rights/layman-access-rights.component.html b/projects/hslayers/common/layman/access-rights/layman-access-rights.component.html
index f7ea90e03f..102ea2edd0 100644
--- a/projects/hslayers/common/layman/access-rights/layman-access-rights.component.html
+++ b/projects/hslayers/common/layman/access-rights/layman-access-rights.component.html
@@ -56,11 +56,11 @@
@for (user of allUsers | filter: userFilter; track user) {
+ [class.bg-primary-subtle]="user.username === hsCommonLaymanService.user()">
{{user.hslDisplayName}}
@for (type of rightsOptions; track type) {
}
diff --git a/projects/hslayers/common/layman/access-rights/layman-access-rights.component.ts b/projects/hslayers/common/layman/access-rights/layman-access-rights.component.ts
index 15dc024137..497fde3576 100644
--- a/projects/hslayers/common/layman/access-rights/layman-access-rights.component.ts
+++ b/projects/hslayers/common/layman/access-rights/layman-access-rights.component.ts
@@ -68,12 +68,12 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
userSearch: string;
endpoint: HsEndpoint;
constructor(
- private hsCommonLaymanService: HsCommonLaymanService,
+ public hsCommonLaymanService: HsCommonLaymanService,
private $http: HttpClient,
private hsLog: HsLogService,
) {}
async ngOnInit(): Promise
{
- this.endpoint = this.hsCommonLaymanService.layman;
+ this.endpoint = this.hsCommonLaymanService.layman();
this.defaultAccessRights = JSON.parse(JSON.stringify(this.access_rights));
this.setUpAccessRights();
}
@@ -113,7 +113,7 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
this.allUsers.length / 2 >= this.allUsers.filter((u) => u[type]).length;
this.allUsers.forEach((user) => {
- const isCurrentUser = user.name === this.endpoint.user;
+ const isCurrentUser = user.name === this.hsCommonLaymanService.user();
//Value for current user won't be changed
user[type] = isCurrentUser ? user[type] : value;
//In case write permission is being added make sure read is granted as well
@@ -199,7 +199,7 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
: [
...this.allRoles,
//Add current user as he has got to retain both read and write rights
- {name: this.endpoint.user, read: true, write: true},
+ {name: this.hsCommonLaymanService.user(), read: true, write: true},
];
this.access_rights[`access_rights.${type}`] = source
.reduce((acc, curr) => {
@@ -291,7 +291,7 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
* Get user roles
*/
async getRoles(access_rights?: AccessRightsModel): Promise {
- if (this.endpoint?.authenticated) {
+ if (this.hsCommonLaymanService.isAuthenticated()) {
const url = `${this.endpoint.url}/rest/roles`;
access_rights ??= this.access_rights;
@@ -390,7 +390,7 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
* Get all registered users from Layman's endpoint service
*/
async getAllUsers(access_rights?: AccessRightsModel): Promise {
- if (this.endpoint?.authenticated) {
+ if (this.hsCommonLaymanService.isAuthenticated()) {
access_rights ??= this.access_rights;
const read = access_rights[AccessRights.READ].split(',');
const write = access_rights[AccessRights.WRITE].split(',');
@@ -403,7 +403,8 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
),
map((res: LaymanUser[]) => {
return res.map((user) => {
- const isCurrentUser = user.username === this.endpoint.user;
+ const isCurrentUser =
+ user.username === this.hsCommonLaymanService.user();
const laymanUser: LaymanUser = {
...user,
name: user.username,
diff --git a/projects/hslayers/common/layman/layman-current-user.component.html b/projects/hslayers/common/layman/layman-current-user.component.html
index 02dc1f95e4..d5cf73668c 100644
--- a/projects/hslayers/common/layman/layman-current-user.component.html
+++ b/projects/hslayers/common/layman/layman-current-user.component.html
@@ -1,23 +1,53 @@
-@if ((inAppLogin | async) && endpoint) {
-
-}
\ No newline at end of file
+@if ((inAppLogin | async) && hsCommonLaymanService.layman()) {
+
+ @if (!hsCommonLaymanService.isAuthenticated()) {
+ @if (hsCommonLaymanService.isAuthenticating()) {
+
+ } @else {
+
+ }
+ } @else {
+
+
+
+
+ }
+
+}
diff --git a/projects/hslayers/common/layman/layman-current-user.component.ts b/projects/hslayers/common/layman/layman-current-user.component.ts
index 37c10785eb..fd6d5c694b 100644
--- a/projects/hslayers/common/layman/layman-current-user.component.ts
+++ b/projects/hslayers/common/layman/layman-current-user.component.ts
@@ -1,103 +1,108 @@
-import {Component, Input, OnInit} from '@angular/core';
-import {Observable, map} from 'rxjs';
+import {Component, computed, inject, signal} from '@angular/core';
+import {map} from 'rxjs';
import {HsCommonLaymanService} from './layman.service';
import {HsDialogContainerService} from 'hslayers-ng/common/dialogs';
-import {HsEndpoint} from 'hslayers-ng/types';
-import {HsLaymanLoginComponent} from './layman-login.component';
+import {HsConfig} from 'hslayers-ng/config';
+import {HsToastService} from 'hslayers-ng/common/toast';
@Component({
selector: 'hs-layman-current-user',
templateUrl: './layman-current-user.component.html',
standalone: false,
-})
-export class HsLaymanCurrentUserComponent implements OnInit {
- @Input() endpoint?: HsEndpoint;
- monitorTries = 0;
- DEFAULT_TIMER_INTERVAL = 2000;
- MAX_MONITOR_TRIES = 100;
- timerInterval = this.DEFAULT_TIMER_INTERVAL;
- getCurrentUserTimer;
+ styles: `
+ .user-auth-container {
+ .user-dropdown {
+ .user-avatar {
+ width: 1.75rem;
+ height: 1.75rem;
+ background-color: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.875rem;
+ }
- /**
- * Controls availability of "Log in" button in HSL components.
- * Not available for Wagtail endpoints as login is handled via separate hub proxy
- */
- inAppLogin: Observable;
- constructor(
- public HsCommonLaymanService: HsCommonLaymanService,
- public HsDialogContainerService: HsDialogContainerService,
- ) {}
+ .user-dropdown-menu {
+ min-width: 15rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
- ngOnInit(): void {
- this.inAppLogin = this.HsCommonLaymanService.layman$.pipe(
- map((layman) => {
- if (layman) {
- //Assign received layman endpoint to local variable
- this.endpoint = layman;
- return this.endpoint?.type === 'layman';
- }
- }),
- );
- }
+ .user-header {
+ border-radius: 0.5rem 0.5rem 0 0;
- isAuthenticated() {
- return this.HsCommonLaymanService.isAuthenticated();
- }
+ .user-avatar-large {
+ width: 3rem;
+ height: 3rem;
+ background-color: #e9ecef;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+ }
+ }
- logout(): void {
- this.monitorUser();
- this.HsCommonLaymanService.logout(this.endpoint);
- }
+ button.dropdown-item {
+ border-radius: 0.25rem;
+ margin: 0.125rem 0;
- sameDomain() {
- const endpointUrl = new URL(this.endpoint.url);
- return (
- location.protocol == endpointUrl.protocol &&
- location.host == endpointUrl.host
- );
- }
+ &:hover {
+ background-color: #f8f9fa;
+ }
- authUrl() {
- return this.endpoint.url + '/login';
- }
+ &.text-danger:hover {
+ background-color: #fff5f5;
+ }
+ }
+ }
+ }
+ }
+ `,
+})
+export class HsLaymanCurrentUserComponent {
+ hsCommonLaymanService = inject(HsCommonLaymanService);
+ HsDialogContainerService = inject(HsDialogContainerService);
+ hsConfig = inject(HsConfig);
+ hsToastService = inject(HsToastService);
+
+ dropdownOpen = signal(false);
+
+ sameDomain = computed(() => {
+ const laymanEndpoint = this.hsCommonLaymanService.layman();
+ if (!laymanEndpoint) {
+ return false;
+ }
+ return laymanEndpoint.url.includes(window.location.origin);
+ });
+
+ inAppLogin = this.hsCommonLaymanService.layman$.pipe(
+ map((layman) => layman?.type === 'layman'),
+ );
/**
- * Periodically poll layman client endpoint for auth change.
- * This is used for hiding login iframe and toggling state for login buttons,
- * which is done in separate modules by subscribing to HsCommonLaymanService.authChange
+ * Log out the current user
*/
- monitorUser(): void {
- if (this.getCurrentUserTimer) {
- clearTimeout(this.getCurrentUserTimer);
- }
- this.monitorTries = 0;
- this.timerInterval = this.DEFAULT_TIMER_INTERVAL;
- const poll = () => {
- this.HsCommonLaymanService.detectAuthChange(this.endpoint).then(
- (somethingChanged) => {
- if (somethingChanged && this.getCurrentUserTimer) {
- clearTimeout(this.getCurrentUserTimer);
- this.monitorTries = this.MAX_MONITOR_TRIES;
- }
- },
- );
- this.monitorTries++;
- if (this.monitorTries > this.MAX_MONITOR_TRIES) {
- clearTimeout(this.getCurrentUserTimer);
- }
- this.getCurrentUserTimer = setTimeout(poll, this.timerInterval);
- };
- this.getCurrentUserTimer = setTimeout(poll, this.timerInterval);
+ logout(): void {
+ this.hsCommonLaymanService.logout();
}
- login(): void {
- this.monitorUser();
+ /**
+ * Open login dialog or redirect to login page
+ */
+ async login(): Promise {
+ this.hsCommonLaymanService.login$.next();
+ const authUrl = this.hsCommonLaymanService.layman()?.url + '/login';
if (!this.sameDomain()) {
+ window.open(authUrl, 'AuthWindow');
return;
}
+
+ // For same-domain logins, open the dialog
+ const {HsLaymanLoginComponent} = await import('./layman-login.component');
this.HsDialogContainerService.create(HsLaymanLoginComponent, {
- url: this.authUrl(),
+ url: authUrl,
});
}
}
diff --git a/projects/hslayers/common/layman/layman-login.component.html b/projects/hslayers/common/layman/layman-login.component.html
index 191ab7654c..1de2bb6b3d 100644
--- a/projects/hslayers/common/layman/layman-login.component.html
+++ b/projects/hslayers/common/layman/layman-login.component.html
@@ -3,7 +3,7 @@
-
\ No newline at end of file
+
diff --git a/projects/hslayers/common/layman/layman-login.component.ts b/projects/hslayers/common/layman/layman-login.component.ts
index 29a9bfd4c2..4affef4d44 100644
--- a/projects/hslayers/common/layman/layman-login.component.ts
+++ b/projects/hslayers/common/layman/layman-login.component.ts
@@ -1,41 +1,36 @@
-import {Component, Input, OnDestroy, OnInit, ViewRef} from '@angular/core';
+import {Component, Input, OnInit, ViewRef} from '@angular/core';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
-import {Subscription} from 'rxjs';
-
import {HsCommonLaymanService} from './layman.service';
import {
HsDialogComponent,
HsDialogContainerService,
} from 'hslayers-ng/common/dialogs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'hs-layman-login',
templateUrl: './layman-login.component.html',
standalone: false,
})
-export class HsLaymanLoginComponent
- implements HsDialogComponent, OnDestroy, OnInit
-{
+export class HsLaymanLoginComponent implements HsDialogComponent, OnInit {
@Input() data: {
url: string;
};
viewRef: ViewRef;
url: SafeResourceUrl;
- authChangeSubscription: Subscription;
constructor(
public HsCommonLaymanService: HsCommonLaymanService,
public HsDialogContainerService: HsDialogContainerService,
private sanitizer: DomSanitizer,
) {
- this.authChangeSubscription =
- this.HsCommonLaymanService.authChange.subscribe(() => {
+ this.HsCommonLaymanService.layman$
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
this.close();
});
}
- ngOnDestroy(): void {
- this.authChangeSubscription.unsubscribe();
- }
+
ngOnInit(): void {
this.url = this.sanitizer.bypassSecurityTrustResourceUrl(this.data.url);
}
diff --git a/projects/hslayers/common/layman/layman.module.ts b/projects/hslayers/common/layman/layman.module.ts
index f30623351a..74f1fa48f9 100644
--- a/projects/hslayers/common/layman/layman.module.ts
+++ b/projects/hslayers/common/layman/layman.module.ts
@@ -7,6 +7,7 @@ import {HsCommonLaymanAccessRightsComponent} from './access-rights/layman-access
import {HsLaymanCurrentUserComponent} from './layman-current-user.component';
import {HsLaymanLoginComponent} from './layman-login.component';
import {TranslateCustomPipe} from 'hslayers-ng/services/language';
+import {NgbDropdownModule} from '@ng-bootstrap/ng-bootstrap';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,7 +16,13 @@ import {TranslateCustomPipe} from 'hslayers-ng/services/language';
HsLaymanCurrentUserComponent,
HsCommonLaymanAccessRightsComponent,
],
- imports: [CommonModule, TranslateCustomPipe, FilterPipe, FormsModule],
+ imports: [
+ CommonModule,
+ TranslateCustomPipe,
+ FilterPipe,
+ FormsModule,
+ NgbDropdownModule,
+ ],
exports: [
HsLaymanLoginComponent,
HsLaymanCurrentUserComponent,
diff --git a/projects/hslayers/common/layman/layman.service.ts b/projects/hslayers/common/layman/layman.service.ts
index ab3175e597..2bd1606411 100644
--- a/projects/hslayers/common/layman/layman.service.ts
+++ b/projects/hslayers/common/layman/layman.service.ts
@@ -1,28 +1,218 @@
import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {Injectable} from '@angular/core';
+import {computed, inject, Injectable} from '@angular/core';
+import {toObservable, toSignal} from '@angular/core/rxjs-interop';
-import {BehaviorSubject, Subject, lastValueFrom, map} from 'rxjs';
+import {
+ Observable,
+ Subject,
+ catchError,
+ distinctUntilChanged,
+ lastValueFrom,
+ map,
+ merge,
+ of,
+ retry,
+ shareReplay,
+ startWith,
+ switchMap,
+ tap,
+ timer,
+} from 'rxjs';
import {CurrentUserResponse} from './types/current-user-response.type';
-import {HsEndpoint} from 'hslayers-ng/types';
+import {AboutLayman, HsEndpoint} from 'hslayers-ng/types';
import {HsLanguageService} from 'hslayers-ng/services/language';
import {HsLogService} from 'hslayers-ng/services/log';
import {HsToastService} from 'hslayers-ng/common/toast';
+import {HsCommonEndpointsService} from 'hslayers-ng/services/endpoints';
+import {HsUtilsService} from 'hslayers-ng/services/utils';
import {parseBase64Style} from './parse-base64-style';
+import {AuthAction, AuthState} from 'hslayers-ng/types/authentication';
@Injectable({
providedIn: 'root',
})
export class HsCommonLaymanService {
- authChange: Subject = new Subject();
+ endpointService = inject(HsCommonEndpointsService);
+ http = inject(HttpClient);
+ hsUtilsService = inject(HsUtilsService);
- layman$: BehaviorSubject = new BehaviorSubject(
- undefined,
+ private readonly MAX_USER_POLL_ATTEMPTS = 7;
+ private readonly USER_POLL_DELAY = 2500;
+
+ readonly layman$: Observable = toObservable(
+ this.endpointService.endpoints,
+ ).pipe(
+ map((endpoints) => endpoints.find((ep) => ep.type.includes('layman'))),
+ switchMap((endpoint) => {
+ if (!endpoint) {
+ return of(undefined);
+ }
+ // Query version information when endpoint is available
+ return this.http
+ .get(endpoint.url + '/rest/about/version')
+ .pipe(
+ map((version) => {
+ if (version) {
+ return {
+ ...endpoint,
+ version: version.about.applications.layman.version,
+ };
+ }
+ return endpoint;
+ }),
+ catchError((error) => {
+ this.hsToastService.createToastPopupMessage(
+ 'COMMON.error',
+ 'Could not get layman version',
+ );
+ return of(endpoint);
+ }),
+ );
+ }),
+ // Register the endpoint with HsUtilsService to avoid circular dependency
+ tap((endpoint) => {
+ if (endpoint) {
+ this.hsUtilsService.registerLaymanEndpoints(endpoint.url);
+ }
+ }),
+ shareReplay(1),
);
- public get layman(): HsEndpoint {
- return this.layman$.getValue();
- }
+ readonly layman = toSignal(this.layman$);
+
+ // Action streams
+ login$ = new Subject();
+ logout$ = new Subject();
+
+ // Combined auth actions
+ authActions$ = merge(
+ this.login$.pipe(map(() => ({type: 'login'}))),
+ this.logout$.pipe(map(() => ({type: 'logout'}))),
+ );
+
+ authState$: Observable = this.layman$.pipe(
+ // Initial check for current user when endpoint is available
+ switchMap((endpoint) => {
+ if (!endpoint) {
+ return of(undefined);
+ }
+ /**
+ * Check for current user. In wagtail we might recive authentication right of the bat.
+ * The rest is controlled via login and logout triggers.
+ */
+ return this.getCurrentUser(endpoint.url).pipe(
+ // Listen for auth actions after initial check
+ switchMap((currentUser) => {
+ return this.authActions$.pipe(
+ // Start with the initial state
+ startWith(null),
+ // For each auth action, update the state
+ switchMap((action) => {
+ if (!action) {
+ return of({
+ user: currentUser.username,
+ authenticated: currentUser.authenticated,
+ }); // Initial state
+ }
+
+ if (action.type === 'login') {
+ // On login poll for user
+ return this.pollForUser().pipe(
+ map((cu) => {
+ const authenticated = !!cu?.authenticated;
+ const username = cu?.username;
+
+ // Show success toast if authentication was successful
+ if (authenticated && username) {
+ this.hsToastService.createToastPopupMessage(
+ 'COMMON.success',
+ 'AUTH.loginSuccessful',
+ {
+ type: 'success',
+ serviceCalledFrom: 'HsCommonLaymanService',
+ },
+ );
+ }
+
+ return {
+ user: username,
+ authenticated,
+ };
+ }),
+ catchError((error) => {
+ // Handle authentication errors
+ this.hsToastService.createToastPopupMessage(
+ 'COMMON.error',
+ 'AUTH.loginFailed',
+ {
+ type: 'danger',
+ serviceCalledFrom: 'HsCommonLaymanService',
+ details: [error.message || 'AUTH.loginFailed'],
+ },
+ );
+
+ // Return endpoint with authentication failed
+ return of({
+ user: undefined,
+ authenticated: false,
+ });
+ }),
+ );
+ }
+ if (action.type === 'logout') {
+ this.hsToastService.createToastPopupMessage(
+ 'AUTH.Logout',
+ 'AUTH.logoutSuccessful',
+ {
+ type: 'success',
+ },
+ );
+ // On logout action, clear user data
+ return of({
+ ...endpoint,
+ user: undefined,
+ authenticated: false,
+ });
+ }
+ }),
+ );
+ }),
+ catchError((error) => {
+ // Handle errors from getCurrentUser
+ this.hsToastService.createToastPopupMessage(
+ 'COMMON.Error',
+ 'AUTH.userInfoFailed',
+ {
+ type: 'danger',
+ serviceCalledFrom: 'HsCommonLaymanService',
+ details: [error.message || 'AUTH.userInfoFailed'],
+ },
+ );
+
+ return of(undefined);
+ }),
+ );
+ }),
+ shareReplay(1),
+ );
+
+ authState = toSignal(this.authState$);
+
+ // Always return a boolean value, defaulting to false when layman is undefined
+ isAuthenticated = computed(() => !!this.authState()?.authenticated);
+ user = computed(() => this.authState()?.user);
+
+ // Create an observable for the authenticating state
+ isAuthenticating = toSignal(
+ merge(
+ // Set to true when login action is triggered
+ this.login$.pipe(map(() => true)),
+
+ // Set to false when layman$ emits a new value (authentication completed)
+ this.layman$.pipe(map(() => false)),
+ ).pipe(startWith(false), distinctUntilChanged(), shareReplay(1)),
+ );
constructor(
private $http: HttpClient,
@@ -31,64 +221,83 @@ export class HsCommonLaymanService {
private hsLog: HsLogService,
) {}
- isAuthenticated() {
- return this.layman?.authenticated;
+ /**
+ * Get current user from layman endpoint
+ * @param endpoint - Layman endpoint
+ */
+ getCurrentUser(endpoint_url: HsEndpoint['url']) {
+ if (!endpoint_url) {
+ return of(undefined);
+ }
+ const url = `${endpoint_url}/rest/current-user`;
+ return this.$http.get(url, {withCredentials: true});
}
/**
- * Monitor if authorization state has changed and
- * return true and broadcast authChange event if so .
- * @param endpoint - Endpoint definition - usually Layman
- * @returns Promise true if authorization state changed (user logged in or out)
+ * Log into existing account by opening a login endpoint in new window
+ * and polling for the results
*/
- async detectAuthChange(endpoint: HsEndpoint): Promise {
- const url = `${endpoint.url}/rest/current-user`;
- try {
- const res: CurrentUserResponse = await lastValueFrom(
- this.$http.get(url, {withCredentials: true}),
- );
-
- let somethingChanged = false;
- if (res.code === 32) {
- endpoint.authenticated = false;
- endpoint.user = undefined;
- }
- if (res.username) {
- if (endpoint.user != res.username) {
- endpoint.user = res.username;
- endpoint.authenticated = res.authenticated;
- somethingChanged = true;
- this.authChange.next(endpoint);
- }
- } else {
- if (endpoint.user != undefined) {
- somethingChanged = true;
- }
- endpoint.user = undefined;
- }
- return somethingChanged;
- } catch (e) {
- this.hsLog.warn(e);
- return e;
+ pollForUser(): Observable {
+ const laymanEndpoint = this.layman();
+ if (!laymanEndpoint) {
+ return of(undefined);
}
+
+ return timer(1000).pipe(
+ switchMap(() =>
+ this.getCurrentUser(laymanEndpoint.url).pipe(
+ switchMap((response) => {
+ if (!response || !response.authenticated) {
+ // Handle case where authenticated is false
+ throw new Error('AUTH.notAuthenticated');
+ }
+ return of(response);
+ }),
+ ),
+ ),
+ retry({
+ count: this.MAX_USER_POLL_ATTEMPTS,
+ delay: this.USER_POLL_DELAY,
+ }),
+ catchError((error) => {
+ // Handle error after all retries are exhausted
+ console.error('Error retrieving profile:', error);
+
+ // Show error toast after retries are exhausted
+ this.hsToastService.createToastPopupMessage(
+ 'COMMON.error',
+ 'AUTH.authenticationFailed',
+ {
+ type: 'danger',
+ serviceCalledFrom: 'HsCommonLaymanService',
+ details: [
+ 'AUTH.authenticationFailed',
+ error.message || 'Unknown error occurred',
+ ],
+ },
+ );
+
+ return of(null);
+ }),
+ );
}
- async getCurrentUserIfNeeded(endpoint: HsEndpoint): Promise {
- if (endpoint.type.includes('layman') && endpoint.user === undefined) {
- await this.detectAuthChange(endpoint);
+ async logout(): Promise {
+ const laymanEndpoint = this.layman();
+ if (!laymanEndpoint) {
+ return;
}
- }
- async logout(endpoint): Promise {
- const url = `${endpoint.url}/logout`;
+ const url = `${laymanEndpoint.url}/logout`;
try {
await lastValueFrom(this.$http.get(url, {withCredentials: true}));
} catch (ex) {
this.hsLog.warn(ex);
} finally {
- endpoint.user = undefined;
- endpoint.authenticated = false;
- this.authChange.next(endpoint);
+ /***
+ * TODO: this might go first
+ */
+ this.logout$.next();
}
}
@@ -107,7 +316,7 @@ export class HsCommonLaymanService {
break;
case 32:
simplifiedResponse = 'Authentication failed. Login to the catalogue.';
- this.detectAuthChange(endpoint);
+ //this.detectAuthChange(endpoint);
break;
default:
simplifiedResponse = responseBody.message;
@@ -125,7 +334,11 @@ export class HsCommonLaymanService {
simplifiedResponse,
undefined,
),
- {disableLocalization: true, serviceCalledFrom: 'HsCommonLaymanService'},
+ {
+ disableLocalization: true,
+ serviceCalledFrom: 'HsCommonLaymanService',
+ type: 'danger',
+ },
);
}
diff --git a/projects/hslayers/components/add-data/catalogue/catalogue-list-item/catalogue-list-item.component.ts b/projects/hslayers/components/add-data/catalogue/catalogue-list-item/catalogue-list-item.component.ts
index c3502d4135..e3683beda5 100644
--- a/projects/hslayers/components/add-data/catalogue/catalogue-list-item/catalogue-list-item.component.ts
+++ b/projects/hslayers/components/add-data/catalogue/catalogue-list-item/catalogue-list-item.component.ts
@@ -15,6 +15,7 @@ import {HsDialogContainerService} from 'hslayers-ng/common/dialogs';
import {HsLogService} from 'hslayers-ng/services/log';
import {HsRemoveLayerDialogService} from 'hslayers-ng/common/remove-multiple';
import {HsSetPermissionsDialogComponent} from 'hslayers-ng/common/dialog-set-permissions';
+import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
@Component({
selector: 'hs-catalogue-list-item',
@@ -49,6 +50,7 @@ export class HsCatalogueListItemComponent implements OnInit {
private hsLaymanBrowserService: HsLaymanBrowserService,
private hsLog: HsLogService,
private hsRemoveLayerDialogService: HsRemoveLayerDialogService,
+ private hsCommonLaymanService: HsCommonLaymanService,
) {}
ngOnInit() {
@@ -177,7 +179,7 @@ export class HsCatalogueListItemComponent implements OnInit {
* @param layer - Metadata record of selected layer
*/
async showPermissions(layer: HsAddDataLayerDescriptor): Promise {
- if (!this.layer.endpoint?.authenticated) {
+ if (!this.hsCommonLaymanService.isAuthenticated()) {
return;
}
this.hsDialogContainerService.create(HsSetPermissionsDialogComponent, {
diff --git a/projects/hslayers/components/add-data/catalogue/catalogue.component.html b/projects/hslayers/components/add-data/catalogue/catalogue.component.html
index 6d1de84e07..fd4bdcde2b 100644
--- a/projects/hslayers/components/add-data/catalogue/catalogue.component.html
+++ b/projects/hslayers/components/add-data/catalogue/catalogue.component.html
@@ -1,4 +1,3 @@
-@if ({layman: hsCommonLaymanService.layman$ | async }; as ctx) {
@@ -18,7 +17,7 @@
name="filterByExtent">
{{'COMPOSITIONS.filterByMap' | translateHs }}
- @if (ctx.layman?.authenticated) {
+ @if (hsCommonLaymanService.isAuthenticated()) {
@@ -138,4 +137,3 @@
}
-}
diff --git a/projects/hslayers/components/add-data/common/new-layer-form/new-layer-form.component.html b/projects/hslayers/components/add-data/common/new-layer-form/new-layer-form.component.html
index 4697bca117..713945dba3 100644
--- a/projects/hslayers/components/add-data/common/new-layer-form/new-layer-form.component.html
+++ b/projects/hslayers/components/add-data/common/new-layer-form/new-layer-form.component.html
@@ -18,7 +18,7 @@
-
-
+
@@ -50,7 +49,7 @@
(change)='loadFilteredCompositions()' name="hsCompositionsCatalogueService.filterByExtent">
{{'COMPOSITIONS.filterByMap' | translateHs }}
- @if (ctx.layman?.authenticated) {
+ @if (hsCommonLaymanService.isAuthenticated()) {
{
- for (const endpoint of this.hsCommonEndpointsService.endpoints) {
+ for (const endpoint of this.hsCommonEndpointsService.endpoints()) {
const record =
this.hsCompositionsMapService.getFeatureRecordAndUnhighlight(
e.feature,
@@ -118,7 +118,7 @@ export class HsCompositionsService {
* Reset composition counters for datasource endpoints
*/
resetCompositionCounter(): void {
- this.hsCommonEndpointsService.endpoints.forEach((ep) => {
+ this.hsCommonEndpointsService.endpoints().forEach((ep) => {
switch (ep.type) {
case 'micka':
return this.hsCompositionsMickaService.resetCompositionCounter(ep);
diff --git a/projects/hslayers/components/compositions/endpoints/compositions-layman.service.ts b/projects/hslayers/components/compositions/endpoints/compositions-layman.service.ts
index 7ac3a7e83d..23286c84fb 100644
--- a/projects/hslayers/components/compositions/endpoints/compositions-layman.service.ts
+++ b/projects/hslayers/components/compositions/endpoints/compositions-layman.service.ts
@@ -46,9 +46,8 @@ export class HsCompositionsLaymanService {
extentFeatureCreated,
_bbox,
): Observable
{
- endpoint.getCurrentUserIfNeeded(endpoint);
endpoint.compositionsPaging.loaded = false;
- const loggedIn = endpoint.authenticated;
+ const loggedIn = this.hsCommonLaymanService.isAuthenticated();
const query = params.query.title ? params.query.title : '';
const sortBy =
params.sortBy == 'date:D'
@@ -65,11 +64,12 @@ export class HsCompositionsLaymanService {
this.hsMapService.getCurrentProj(),
'EPSG:3857',
);
- const bbox = params.filterByExtent ? b.join(',') : '';
+ const bbox = params.filterByExtent ? b.join(',') : '';
+ const workspace = this.hsCommonLaymanService.user();
const withPermissionOrMine = params.filterByOnlyMine
? loggedIn
- ? `workspaces/${endpoint.user}/`
+ ? `workspaces/${workspace}/`
: ''
: '';
const url = `${endpoint.url}/rest/${withPermissionOrMine}maps`;
@@ -154,6 +154,7 @@ export class HsCompositionsLaymanService {
? parseInt(response.headers.get('x-total-count'))
: response.body.length;
+ const currentUser = this.hsCommonLaymanService.user();
endpoint.compositions = response.body.map((record) => {
const tmp: HsMapCompositionDescriptor = {
name: record.name,
@@ -162,7 +163,7 @@ export class HsCompositionsLaymanService {
featureId: undefined,
highlighted: false,
editable: record.access_rights.write.some((user) => {
- return [endpoint.user, 'EVERYONE'].includes(user);
+ return [currentUser, 'EVERYONE'].includes(user);
}),
url: `${endpoint.url}/rest/workspaces/${record.workspace}/maps/${record.name}`,
endpoint,
diff --git a/projects/hslayers/components/draw/draw-layer-metadata/draw-layer-metadata.component.html b/projects/hslayers/components/draw/draw-layer-metadata/draw-layer-metadata.component.html
index 06f8ec1af4..23dece7f1d 100644
--- a/projects/hslayers/components/draw/draw-layer-metadata/draw-layer-metadata.component.html
+++ b/projects/hslayers/components/draw/draw-layer-metadata/draw-layer-metadata.component.html
@@ -19,7 +19,7 @@
{{'DRAW.notAuthorized' | translateHs }}
-
+
}
@if (data.service.drawableLaymanLayers.length > 0 && data.service.isAuthenticated) {
@@ -45,8 +45,7 @@
translateHs }}
@if (data.service.isAuthenticated) {
-
-
+
}
{{'COMMON.advancedOptions' |
diff --git a/projects/hslayers/components/layer-manager/layer-manager.component.ts b/projects/hslayers/components/layer-manager/layer-manager.component.ts
index f18a59bee5..b0e6efaccc 100644
--- a/projects/hslayers/components/layer-manager/layer-manager.component.ts
+++ b/projects/hslayers/components/layer-manager/layer-manager.component.ts
@@ -355,7 +355,7 @@ export class HsLayerManagerComponent
.map((l) => {
return l.layer;
}),
- this.hsCommonLaymanService.layman?.authenticated
+ this.hsCommonLaymanService.isAuthenticated()
? ['map', 'mapcatalogue']
: ['map'],
);
diff --git a/projects/hslayers/components/save-map/dialog-result/dialog-result.component.ts b/projects/hslayers/components/save-map/dialog-result/dialog-result.component.ts
index e146e3dafc..a0273c4832 100644
--- a/projects/hslayers/components/save-map/dialog-result/dialog-result.component.ts
+++ b/projects/hslayers/components/save-map/dialog-result/dialog-result.component.ts
@@ -35,7 +35,7 @@ export class HsSaveMapResultDialogComponent implements HsDialogComponent {
* Thus - this has to always be making a request for current user workspace
*/
this.hsSaveMapManagerService.compoData.patchValue({
- workspace: this.hsSaveMapManagerService.currentUser,
+ workspace: this.hsSaveMapManagerService.currentUser(),
});
await this.hsSaveMapManagerService.initiateSave(newSave);
diff --git a/projects/hslayers/components/save-map/form/form.component.html b/projects/hslayers/components/save-map/form/form.component.html
index 33ffc9ebfc..66d3e93884 100644
--- a/projects/hslayers/components/save-map/form/form.component.html
+++ b/projects/hslayers/components/save-map/form/form.component.html
@@ -22,7 +22,7 @@
- @if (endpoint?.type.includes('layman')) {
+ @if (endpoint()?.type.includes('layman')) {
@@ -65,4 +65,4 @@
}
-}
\ No newline at end of file
+}
diff --git a/projects/hslayers/components/save-map/form/form.component.ts b/projects/hslayers/components/save-map/form/form.component.ts
index 9e8d112a5f..9902e1126d 100644
--- a/projects/hslayers/components/save-map/form/form.component.ts
+++ b/projects/hslayers/components/save-map/form/form.component.ts
@@ -1,5 +1,5 @@
import {Component, DestroyRef, OnInit, inject} from '@angular/core';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {takeUntilDestroyed, toSignal} from '@angular/core/rxjs-interop';
import {Observable, map, startWith} from 'rxjs';
@@ -14,7 +14,9 @@ import {HsUtilsService} from 'hslayers-ng/services/utils';
standalone: false,
})
export class HsSaveMapAdvancedFormComponent implements OnInit {
- endpoint: HsEndpoint;
+ endpoint = toSignal(this.hsSaveMapManagerService.endpointSelected, {
+ initialValue: null,
+ });
overwrite = false;
downloadableData: string;
extraFormOpened = '';
@@ -36,12 +38,6 @@ export class HsSaveMapAdvancedFormComponent implements OnInit {
}
ngOnInit(): void {
- this.hsSaveMapManagerService.endpointSelected
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((endpoint) => {
- this.endpoint = endpoint;
- });
-
this.hsSaveMapManagerService.saveMapResulted
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((statusData) => {
@@ -136,7 +132,7 @@ export class HsSaveMapAdvancedFormComponent implements OnInit {
if (this.endpoint === null) {
return false;
}
- if (this.endpoint.type.includes('layman')) {
+ if (this.endpoint().type.includes('layman')) {
return true;
}
}
@@ -150,7 +146,7 @@ export class HsSaveMapAdvancedFormComponent implements OnInit {
* Overwriting composition of other user and making it private
* = access for owner + current user
*/
- const currentUser = this.hsSaveMapManagerService.currentUser;
+ const currentUser = this.hsSaveMapManagerService.currentUser();
const workspace =
this.hsSaveMapManagerService.compoData.get('workspace').value;
if (newSave == false && this.canOverwrite() && currentUser !== workspace) {
diff --git a/projects/hslayers/components/save-map/save-map-manager.service.ts b/projects/hslayers/components/save-map/save-map-manager.service.ts
index 54f8e6a886..3bae1a0ccf 100644
--- a/projects/hslayers/components/save-map/save-map-manager.service.ts
+++ b/projects/hslayers/components/save-map/save-map-manager.service.ts
@@ -1,7 +1,7 @@
import {BehaviorSubject, Subject, withLatestFrom} from 'rxjs';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {HttpClient} from '@angular/common/http';
-import {Injectable} from '@angular/core';
+import {computed, Injectable, Signal} from '@angular/core';
import {
AccessRightsModel,
@@ -25,6 +25,7 @@ import {HsLogService} from 'hslayers-ng/services/log';
import {HsMapService} from 'hslayers-ng/services/map';
import {HsShareService} from 'hslayers-ng/components/share';
import {HsUtilsService} from 'hslayers-ng/services/utils';
+import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
export class HsSaveMapManagerParams {
statusData: StatusData = {
@@ -65,7 +66,7 @@ export class HsSaveMapManagerParams {
endpointSelected: BehaviorSubject
= new BehaviorSubject(null);
preSaveCheckCompleted: Subject = new Subject();
changeTitle: boolean;
- currentUser: string;
+ currentUser: Signal;
missingName = false;
missingAbstract = false;
@@ -76,6 +77,8 @@ export class HsSaveMapManagerParams {
providedIn: 'root',
})
export class HsSaveMapManagerService extends HsSaveMapManagerParams {
+ currentUser = this.hsCommonLaymanService.user;
+
constructor(
private hsMapService: HsMapService,
private hsSaveMapService: HsSaveMapService,
@@ -88,6 +91,7 @@ export class HsSaveMapManagerService extends HsSaveMapManagerParams {
private hsEventBusService: HsEventBusService,
private hsLogService: HsLogService,
private hsCompositionsParserService: HsCompositionsParserService,
+ private hsCommonLaymanService: HsCommonLaymanService,
) {
super();
@@ -99,7 +103,7 @@ export class HsSaveMapManagerService extends HsSaveMapManagerParams {
this.currentComposition = responseData;
const workspace = metadata['error']
- ? this.currentUser
+ ? this.currentUser()
: this.parseAccessRights(metadata);
this.compoData.patchValue({
@@ -133,11 +137,11 @@ export class HsSaveMapManagerService extends HsSaveMapManagerParams {
: null;
const write = metadata.access_rights.write;
const read = metadata.access_rights.read;
- if (this.currentUser === workspace) {
- this.privateOrPublic(write, 'write', this.currentUser);
- this.privateOrPublic(read, 'read', this.currentUser);
+ if (this.currentUser() === workspace) {
+ this.privateOrPublic(write, 'write', this.currentUser());
+ this.privateOrPublic(read, 'read', this.currentUser());
} else if (
- write.includes(this.currentUser) ||
+ write.includes(this.currentUser()) ||
/**
* Different user + PUBLIC write
*/
diff --git a/projects/hslayers/components/save-map/save-map.component.html b/projects/hslayers/components/save-map/save-map.component.html
index dccd8d36ed..1bd62f5d0c 100644
--- a/projects/hslayers/components/save-map/save-map.component.html
+++ b/projects/hslayers/components/save-map/save-map.component.html
@@ -7,21 +7,22 @@
- @if (endpoint?.type === 'layman') {
-
-
+ @if (selectedEndpoint()?.type === 'layman') {
+
}
- @if (isAuthenticated || !endpoint?.type.includes('layman')) {
-
-
+ @if (isAuthenticated() || !selectedEndpoint()?.type?.includes('layman')) {
+
} @else {
{{'SAVECOMPOSITION.panelMd.youAreNotAuthorized' | translateHs }}
diff --git a/projects/hslayers/components/save-map/save-map.component.ts b/projects/hslayers/components/save-map/save-map.component.ts
index bc38e27a70..1de64e617d 100644
--- a/projects/hslayers/components/save-map/save-map.component.ts
+++ b/projects/hslayers/components/save-map/save-map.component.ts
@@ -1,5 +1,9 @@
-import {Component, OnInit} from '@angular/core';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {Component, linkedSignal, OnInit} from '@angular/core';
+import {
+ takeUntilDestroyed,
+ toObservable,
+ toSignal,
+} from '@angular/core/rxjs-interop';
import {HsCommonEndpointsService} from 'hslayers-ng/services/endpoints';
import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
@@ -16,9 +20,15 @@ import {HsSaveMapService} from 'hslayers-ng/services/save-map';
standalone: false,
})
export class HsSaveMapComponent extends HsPanelBaseComponent implements OnInit {
- endpoint: HsEndpoint = null;
+ selectedEndpoint = toSignal(this.hsSaveMapManagerService.endpointSelected);
+
+ compareEndpoints(a: HsEndpoint, b: HsEndpoint): boolean {
+ // Compare endpoints by their URL or other unique identifier
+ return a?.url === b?.url;
+ }
+
endpoints: HsEndpoint[];
- isAuthenticated = false;
+ isAuthenticated = this.hsCommonLaymanService.isAuthenticated;
name = 'saveMap';
constructor(
private hsConfig: HsConfig,
@@ -30,16 +40,12 @@ export class HsSaveMapComponent extends HsPanelBaseComponent implements OnInit {
private hsSaveMapService: HsSaveMapService,
) {
super();
- }
- ngOnInit() {
- super.ngOnInit();
- this.endpoints = this.hsCommonEndpointsService.endpoints;
- this.hsCommonEndpointsService.endpointsFilled
+ toObservable(this.hsCommonEndpointsService.endpoints)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((endpoints) => {
- if (endpoints?.length > 0 && !this.endpoint) {
- const laymanEp = this.hsCommonLaymanService.layman;
+ if (endpoints?.length > 0 && !this.selectedEndpoint()) {
+ const laymanEp = this.hsCommonLaymanService.layman();
if (laymanEp) {
this.hsSaveMapManagerService.selectEndpoint(laymanEp);
} else {
@@ -47,33 +53,19 @@ export class HsSaveMapComponent extends HsPanelBaseComponent implements OnInit {
}
}
});
-
- this.hsCommonLaymanService.authChange
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((endpoint) => {
- this.isAuthenticated = endpoint.authenticated;
- this.hsSaveMapManagerService.currentUser = endpoint.user;
- });
-
- this.hsSaveMapManagerService.endpointSelected
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((endpoint) => {
- if (endpoint) {
- this.endpoint = endpoint;
- if (endpoint.getCurrentUserIfNeeded) {
- endpoint.getCurrentUserIfNeeded(endpoint);
- }
- }
- });
+ }
+ ngOnInit() {
+ super.ngOnInit();
+ this.endpoints = this.hsCommonEndpointsService.endpoints();
this.hsSaveMapManagerService.panelOpened
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((composition) => {
if (composition && composition.endpoint) {
const openedType = composition.endpoint.type;
- const found = this.hsCommonEndpointsService.endpoints.filter((ep) =>
- ep.type.includes(openedType),
- );
+ const found = this.hsCommonEndpointsService
+ .endpoints()
+ .filter((ep) => ep.type.includes(openedType));
if (found.length > 0) {
this.hsSaveMapManagerService.selectEndpoint(found[0]);
}
diff --git a/projects/hslayers/core/auth.interceptor.ts b/projects/hslayers/core/auth.interceptor.ts
new file mode 100644
index 0000000000..278537e39b
--- /dev/null
+++ b/projects/hslayers/core/auth.interceptor.ts
@@ -0,0 +1,21 @@
+import {HttpInterceptorFn} from '@angular/common/http';
+import {catchError} from 'rxjs/operators';
+import {inject} from '@angular/core';
+import {throwError} from 'rxjs';
+
+import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
+
+export const authInterceptor: HttpInterceptorFn = (req, next) => {
+ const authService = inject(HsCommonLaymanService);
+
+ return next(req).pipe(
+ catchError((err) => {
+ if (err.status === 401 && err?.authenticated === false) {
+ //Unauthorized request sent to backend -> logout
+ authService.logout$.next();
+ }
+ // Re-throw the error so it can be handled elsewhere if needed
+ return throwError(() => err);
+ }),
+ );
+};
diff --git a/projects/hslayers/css/icons/hslayers-ng-fa-icons.css b/projects/hslayers/css/icons/hslayers-ng-fa-icons.css
index b0b19ce53d..e1e1e015c6 100644
--- a/projects/hslayers/css/icons/hslayers-ng-fa-icons.css
+++ b/projects/hslayers/css/icons/hslayers-ng-fa-icons.css
@@ -65,7 +65,7 @@
font-style: normal;
font-weight: 900;
font-display: block;
- src: url(data:font/woff2;charset=utf-8;base64,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) format('woff2');
+ src: url(data:font/woff2;charset=utf-8;base64,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) format('woff2');
}
@font-face {
@@ -73,7 +73,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
- src: url(data:font/woff2;charset=utf-8;base64,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) format('woff2');
+ src: url(data:font/woff2;charset=utf-8;base64,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) format('woff2');
}
@font-face {
@@ -133,6 +133,10 @@
--fa: "\f077";
}
+.fa-user {
+ --fa: "\f007";
+ }
+
.fa-globe {
--fa: "\f0ac";
}
@@ -153,6 +157,10 @@
--fa: "\f715";
}
+.fa-right-to-bracket {
+ --fa: "\f2f6";
+ }
+
.fa-map-location-dot {
--fa: "\f5a0";
}
diff --git a/projects/hslayers/css/icons/icons.txt b/projects/hslayers/css/icons/icons.txt
index 215f5d2753..5cefbb1f0f 100644
--- a/projects/hslayers/css/icons/icons.txt
+++ b/projects/hslayers/css/icons/icons.txt
@@ -74,3 +74,5 @@ fa-triangle-exclamation
fa-circle-info
eraser
globe
+user
+right-to-bracket
diff --git a/projects/hslayers/services/add-data/catalogue/catalogue-map.service.ts b/projects/hslayers/services/add-data/catalogue/catalogue-map.service.ts
index 71daac046d..9044e0b6c9 100644
--- a/projects/hslayers/services/add-data/catalogue/catalogue-map.service.ts
+++ b/projects/hslayers/services/add-data/catalogue/catalogue-map.service.ts
@@ -95,9 +95,9 @@ export class HsAddDataCatalogueMapService {
const featuresUnderMouse = this.extentLayer
.getSource()
.getFeaturesAtCoordinate(evt.coordinate);
- for (const endpoint of this.hsCommonEndpointsService.endpoints.filter(
- (ep) => ep.layers,
- )) {
+ for (const endpoint of this.hsCommonEndpointsService
+ .endpoints()
+ .filter((ep) => ep.layers)) {
this.hsLayerUtilsService.highlightFeatures(
featuresUnderMouse,
this.extentLayer,
diff --git a/projects/hslayers/services/add-data/catalogue/catalogue.service.ts b/projects/hslayers/services/add-data/catalogue/catalogue.service.ts
index 7e449cb70b..83cc8eeb37 100644
--- a/projects/hslayers/services/add-data/catalogue/catalogue.service.ts
+++ b/projects/hslayers/services/add-data/catalogue/catalogue.service.ts
@@ -23,6 +23,7 @@ import {HsLayoutService} from 'hslayers-ng/services/layout';
import {HsMapService} from 'hslayers-ng/services/map';
import {HsMickaBrowserService} from './micka.service';
import {HsUtilsService} from 'hslayers-ng/services/utils';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
class HsAddDataCatalogueParams {
data: any = {
@@ -90,11 +91,13 @@ export class HsAddDataCatalogueService extends HsAddDataCatalogueParams {
this.calcExtentLayerVisibility();
});
- this.hsCommonLaymanService.authChange.subscribe(() => {
- if (this.panelVisible()) {
- this.reloadData();
- }
- });
+ this.hsCommonLaymanService.layman$
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
+ if (this.panelVisible()) {
+ this.reloadData();
+ }
+ });
this.hsAddDataService.datasetTypeSelected.subscribe((type) => {
if (type == 'catalogue' && this.panelVisible()) {
@@ -102,7 +105,7 @@ export class HsAddDataCatalogueService extends HsAddDataCatalogueParams {
}
});
- this.endpoints = this.hsCommonEndpointsService.endpoints;
+ this.endpoints = this.hsCommonEndpointsService.endpoints();
if (this.dataSourceExistsAndEmpty() && this.panelVisible()) {
this.queryCatalogs();
@@ -135,7 +138,7 @@ export class HsAddDataCatalogueService extends HsAddDataCatalogueParams {
}
reloadData(): void {
- this.endpoints = this.hsCommonEndpointsService.endpoints;
+ this.endpoints = this.hsCommonEndpointsService.endpoints();
this.resetList();
this.queryCatalogs();
// this.hsMickaFilterService.fillCodesets();
diff --git a/projects/hslayers/services/add-data/catalogue/layman.service.ts b/projects/hslayers/services/add-data/catalogue/layman.service.ts
index c884dc5008..1e570b0c6a 100644
--- a/projects/hslayers/services/add-data/catalogue/layman.service.ts
+++ b/projects/hslayers/services/add-data/catalogue/layman.service.ts
@@ -52,11 +52,11 @@ export class HsLaymanBrowserService {
},
extentFeatureCreated?: (feature: Feature) => void,
): Observable {
- endpoint.getCurrentUserIfNeeded(endpoint);
- const loggedIn = endpoint.authenticated;
+ const loggedIn = this.hsCommonLaymanService.isAuthenticated();
+ const workspace = this.hsCommonLaymanService.user();
const withPermissionOrMine = data?.onlyMine
? loggedIn
- ? `workspaces/${endpoint.user}/`
+ ? `workspaces/${workspace}/`
: ''
: '';
const url = `${endpoint.url}/rest/${withPermissionOrMine}layers`;
@@ -246,16 +246,13 @@ export class HsLaymanBrowserService {
),
);
if (data.code || data.message) {
- if (data.code == 32) {
- endpoint.user = undefined;
- endpoint.authenticated = false;
- this.hsCommonLaymanService.authChange.next(endpoint);
+ if (data.code != 32) {
+ this.hsToastService.createToastPopupMessage(
+ data.message ?? 'ADDLAYERS.ERROR.errorWhileRequestingLayers',
+ data.detail ?? `${data.code}/${data.sub_code}`,
+ {},
+ );
}
- this.hsToastService.createToastPopupMessage(
- data.message ?? 'ADDLAYERS.ERROR.errorWhileRequestingLayers',
- data.detail ?? `${data.code}/${data.sub_code}`,
- {},
- );
return;
}
diff --git a/projects/hslayers/services/add-data/common-file.service.ts b/projects/hslayers/services/add-data/common-file.service.ts
index 30ad1a4a73..b77af88b77 100644
--- a/projects/hslayers/services/add-data/common-file.service.ts
+++ b/projects/hslayers/services/add-data/common-file.service.ts
@@ -5,7 +5,6 @@ import {Subject} from 'rxjs';
import {HsAddDataOwsService} from './url/add-data-ows.service';
import {HsAddDataService} from './add-data.service';
import {HsAddDataUrlService} from './url/add-data-url.service';
-import {HsCommonEndpointsService} from 'hslayers-ng/services/endpoints';
import {HsLanguageService} from 'hslayers-ng/services/language';
import {HsLaymanService} from 'hslayers-ng/services/save-map';
import {HsLogService} from 'hslayers-ng/services/log';
@@ -45,7 +44,7 @@ export class HsAddDataCommonFileServiceParams {
readingData = false;
loadingToLayman = false;
asyncLoading = false;
- endpoint: HsEndpoint = null;
+ endpoint: HsEndpoint;
/**
* @param success - true when layer added successfully
*/
@@ -60,7 +59,6 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
private hsAddDataOwsService: HsAddDataOwsService,
private hsAddDataUrlService: HsAddDataUrlService,
private hsAddDataService: HsAddDataService,
- private hsCommonEndpointsService: HsCommonEndpointsService,
private hsDialogContainerService: HsDialogContainerService,
private hsLanguageService: HsLanguageService,
private hsLaymanService: HsLaymanService,
@@ -72,12 +70,12 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
super();
}
+ endpoint = this.hsCommonLaymanService.layman();
/**
* Clear service param values to default values
*/
clearParams(): void {
this.asyncLoading = false;
- this.endpoint = null;
this.loadingToLayman = false;
this.hsLaymanService.totalProgress = 0;
}
@@ -115,23 +113,6 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
}
}
- /**
- * From available endpoints picks one
- * - either Layman endpoint if available or any other if not
- */
- pickEndpoint(): void {
- const endpoints = this.hsCommonEndpointsService.endpoints;
- if (endpoints && endpoints.length > 0) {
- const layman = this.hsCommonLaymanService.layman;
- if (layman) {
- this.endpoint = layman;
- this.endpoint.getCurrentUserIfNeeded(this.endpoint);
- } else {
- this.endpoint = endpoints[0];
- }
- }
- }
-
/**
* Get tooltip translated text
* @param data - File data object provided
@@ -236,7 +217,7 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
overwrite?: boolean,
): Promise {
try {
- const formData = await this.constructFormData(endpoint, formDataParams);
+ const formData = await this.constructFormData(formDataParams);
const asyncUpload: AsyncUpload =
this.hsLaymanService.prepareAsyncUpload(formData);
this.asyncLoading = asyncUpload.async;
@@ -263,12 +244,11 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
const friendlyName = getLaymanFriendlyLayerName(name);
let descriptor: HsLaymanLayerDescriptor;
if (this.isAuthenticated()) {
- this.pickEndpoint();
try {
descriptor = await this.hsLaymanService.describeLayer(
this.endpoint,
name,
- this.endpoint.user,
+ this.hsCommonLaymanService.user(),
true,
);
} catch (error) {
@@ -323,7 +303,6 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
/**
* Construct a set of key/value pairs from, that can be easily sent using HTTP
- * @param endpoint - Layman endpoint description (url, name, user)
* @param files - Array of files
* @param name - Name of new layer
* @param title - Title of new layer
@@ -333,10 +312,7 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
* @param access_rights - User access rights for the new layer,
* @returns FormData object for HTTP request
*/
- async constructFormData(
- endpoint: HsEndpoint,
- formDataParams: FileFormData,
- ): Promise {
+ async constructFormData(formDataParams: FileFormData): Promise {
this.readingData = true;
const {files, name, abstract, srs, access_rights, timeRegex} =
formDataParams;
@@ -380,10 +356,8 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
formData.append('abstract', abstract);
formData.append('crs', srs);
- const rights = this.hsLaymanService.parseAccessRightsForLayman(
- endpoint,
- access_rights,
- );
+ const rights =
+ this.hsLaymanService.parseAccessRightsForLayman(access_rights);
formData.append('access_rights.write', rights.write);
formData.append('access_rights.read', rights.read);
@@ -408,7 +382,7 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
try {
this.loadingToLayman = true;
if (!this.endpoint) {
- this.pickEndpoint();
+ throw new Error('No endpoint available');
}
if (!this.isSRSSupported(data)) {
throw new Error(
@@ -598,7 +572,7 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
const descriptor = await this.hsLaymanService.describeLayer(
endpoint,
layerName,
- endpoint.user,
+ this.hsCommonLaymanService.user(),
);
if (
pendingParams.some((param) =>
@@ -640,9 +614,9 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
* @returns True, if srs is supported, false otherwise
*/
isSRSSupported(data: FileDataObject): boolean {
- return this.hsLaymanService.supportedCRRList.some((epsg) =>
- data.srs.endsWith(epsg),
- );
+ return this.hsLaymanService
+ .supportedCRRList()
+ .some((epsg) => data.srs.endsWith(epsg));
}
/**
@@ -650,7 +624,7 @@ export class HsAddDataCommonFileService extends HsAddDataCommonFileServiceParams
* @returns True, if user is authorized, false otherwise
*/
isAuthenticated(): boolean {
- return this.hsCommonLaymanService.layman?.authenticated ?? false;
+ return this.hsCommonLaymanService.isAuthenticated();
}
/**
diff --git a/projects/hslayers/services/add-data/vector/vector-utils.service.ts b/projects/hslayers/services/add-data/vector/vector-utils.service.ts
index a327669891..3688486621 100644
--- a/projects/hslayers/services/add-data/vector/vector-utils.service.ts
+++ b/projects/hslayers/services/add-data/vector/vector-utils.service.ts
@@ -39,7 +39,8 @@ export class HsAddDataVectorUtilsService {
.map((proj) => proj.getCode())
.some((code) => code === projection.getCode())
? getProjection('EPSG:4326')
- : this.hsLaymanService.supportedCRRList.indexOf(projection.getCode()) > -1
+ : this.hsLaymanService.supportedCRRList().indexOf(projection.getCode()) >
+ -1
? projection
: getProjection('EPSG:4326');
//Features in map CRS
diff --git a/projects/hslayers/services/add-data/vector/vector.service.ts b/projects/hslayers/services/add-data/vector/vector.service.ts
index 5dc92c1eae..bc2563bdd8 100644
--- a/projects/hslayers/services/add-data/vector/vector.service.ts
+++ b/projects/hslayers/services/add-data/vector/vector.service.ts
@@ -228,7 +228,7 @@ export class HsAddDataVectorService {
idAttribute: `?${data.idProperty}`,
path: this.hsUtilsService.undefineEmptyString(data.folder_name),
access_rights: data.access_rights,
- workspace: this.hsAddDataCommonFileService.endpoint?.user,
+ workspace: this.hsCommonLaymanService.user(),
query: data.query,
queryCapabilities:
data.type != 'kml' &&
@@ -248,7 +248,8 @@ export class HsAddDataVectorService {
*/
async addNewLayer(data: VectorDataObject) {
if (!this.hsAddDataCommonFileService.endpoint) {
- this.hsAddDataCommonFileService.pickEndpoint();
+ console.error('No endpoint available');
+ return;
}
const addLayerRes: {
layer: VectorLayer>;
@@ -312,9 +313,9 @@ export class HsAddDataVectorService {
async upsertLayer(data: VectorDataObject): Promise {
const commonFileRef = this.hsAddDataCommonFileService;
- const crsSupported = this.hsLaymanService.supportedCRRList.includes(
- data.nativeSRS,
- );
+ const crsSupported = this.hsLaymanService
+ .supportedCRRList()
+ .includes(data.nativeSRS);
const style =
typeof data.serializedStyle == 'string'
? data.serializedStyle
@@ -325,7 +326,7 @@ export class HsAddDataVectorService {
crs: this.hsAddDataVectorUtilsService
.getFeaturesProjection(getProjection(data.nativeSRS))
.getCode(),
- workspace: commonFileRef.endpoint.user,
+ workspace: this.hsCommonLaymanService.user(),
access_rights: data.access_rights,
style,
};
@@ -382,7 +383,7 @@ export class HsAddDataVectorService {
await this.hsLaymanService.describeLayer(
commonFileRef.endpoint,
upsertReq.name,
- commonFileRef.endpoint.user,
+ this.hsCommonLaymanService.user(),
);
return OverwriteResponse.overwrite;
}
diff --git a/projects/hslayers/services/compositions/compositions-parser.service.ts b/projects/hslayers/services/compositions/compositions-parser.service.ts
index bfe8033858..23af2c6a74 100644
--- a/projects/hslayers/services/compositions/compositions-parser.service.ts
+++ b/projects/hslayers/services/compositions/compositions-parser.service.ts
@@ -105,14 +105,14 @@ export class HsCompositionsParserService {
switchMap((_) => {
const fromLayman = isLaymanUrl(
this.current_composition_url,
- this.hsCommonLaymanService.layman,
+ this.hsCommonLaymanService.layman(),
);
return this.$http
.get(
this.current_composition_url.replace('/file', ''),
{
withCredentials:
- this.hsCommonLaymanService.layman?.user && fromLayman,
+ this.hsCommonLaymanService.isAuthenticated() && fromLayman,
},
)
.pipe(
@@ -160,8 +160,8 @@ export class HsCompositionsParserService {
options['responseType'] = 'text';
}
options['withCredentials'] =
- isLaymanUrl(url, this.hsCommonLaymanService.layman) &&
- this.hsCommonLaymanService.layman.user;
+ isLaymanUrl(url, this.hsCommonLaymanService.layman()) &&
+ this.hsCommonLaymanService.isAuthenticated();
const data: any = await lastValueFrom(this.$http.get(url, options)).catch(
(e) => {
@@ -576,7 +576,7 @@ export class HsCompositionsParserService {
response = await lastValueFrom(
this.$http.get(url, {
responseType: 'json',
- withCredentials: !!this.hsCommonLaymanService.layman.user,
+ withCredentials: !!this.hsCommonLaymanService.isAuthenticated(),
}),
);
}
diff --git a/projects/hslayers/services/draw/draw.service.params.ts b/projects/hslayers/services/draw/draw.service.params.ts
index 19d8c0207d..6bc9030c9b 100644
--- a/projects/hslayers/services/draw/draw.service.params.ts
+++ b/projects/hslayers/services/draw/draw.service.params.ts
@@ -4,6 +4,7 @@ import {EventsKey} from 'ol/events';
import {Layer, Vector as VectorLayer} from 'ol/layer';
import {Source, Vector as VectorSource} from 'ol/source';
import {Subject} from 'rxjs';
+import {Signal} from '@angular/core';
export class HsDrawServiceParams {
drawableLayers: Array> = [];
@@ -54,7 +55,7 @@ export class HsDrawServiceParams {
onDeselected: any;
laymanEndpoint: any;
previouslySelected: any;
- isAuthenticated: boolean;
+ isAuthenticated: Signal;
onlyMine = true;
addedLayersRemoved = false;
eventHandlers: EventsKey[] = [];
diff --git a/projects/hslayers/services/draw/draw.service.ts b/projects/hslayers/services/draw/draw.service.ts
index cb750b148b..771aed5ed5 100644
--- a/projects/hslayers/services/draw/draw.service.ts
+++ b/projects/hslayers/services/draw/draw.service.ts
@@ -50,6 +50,7 @@ import {
setTitle,
setWorkspace,
} from 'hslayers-ng/common/extensions';
+import {takeUntilDestroyed, toObservable} from '@angular/core/rxjs-interop';
type ActivateParams = {
onDrawStart?;
@@ -66,6 +67,8 @@ export const TMP_LAYER_TITLE = 'tmpDrawLayer';
providedIn: 'root',
})
export class HsDrawService extends HsDrawServiceParams {
+ isAuthenticated = this.hsCommonLaymanService.isAuthenticated;
+
constructor(
public hsMapService: HsMapService,
public hsLayerUtilsService: HsLayerUtilsService,
@@ -139,22 +142,23 @@ export class HsDrawService extends HsDrawServiceParams {
this.fillDrawableLayers();
}
});
+ });
- this.hsCommonLaymanService.authChange.subscribe((endpoint) => {
+ toObservable(this.hsCommonLaymanService.isAuthenticated)
+ .pipe(takeUntilDestroyed())
+ .subscribe((isAuthenticated) => {
this.fillDrawableLayers();
- this.isAuthenticated = endpoint?.authenticated;
//When metadata dialog window opened. Layer is being added
if (this.selectedLayer && this.tmpDrawLayer) {
- setWorkspace(this.selectedLayer, endpoint?.user);
+ setWorkspace(this.selectedLayer, this.hsCommonLaymanService.user());
setDefinition(this.selectedLayer, {
- format: this.isAuthenticated ? 'WFS' : null,
- url: this.isAuthenticated
- ? this.hsCommonLaymanService.layman?.url + '/wfs'
+ format: isAuthenticated ? 'WFS' : null,
+ url: isAuthenticated
+ ? this.hsCommonLaymanService.layman()?.url + '/wfs'
: null,
});
}
});
- });
}
/**
@@ -205,7 +209,7 @@ export class HsDrawService extends HsDrawServiceParams {
while (this.hsMapService.findLayerByTitle(tmpTitle)) {
tmpTitle = `${this.translate('DRAW.drawLayer')} ${i++}`;
}
- const layman = this.hsCommonLaymanService.layman;
+ const layman = this.hsCommonLaymanService.layman();
const drawLayer = new VectorLayer>({
//TODO: Also name should be set, but take care in case a layer with that name already exists in layman
source: tmpSource,
@@ -221,7 +225,7 @@ export class HsDrawService extends HsDrawServiceParams {
format: this.isAuthenticated ? 'WFS' : null,
url: this.isAuthenticated ? layman.url + '/wfs' : null,
});
- setWorkspace(drawLayer, layman?.user);
+ setWorkspace(drawLayer, this.hsCommonLaymanService.user());
this.tmpDrawLayer = true;
this.selectedLayer = drawLayer;
this.layerMetadataDialog.next();
@@ -516,7 +520,7 @@ export class HsDrawService extends HsDrawServiceParams {
}
this.addedLayersRemoved = false;
this.drawableLayers = drawables;
- this.laymanEndpoint = this.hsCommonLaymanService.layman;
+ this.laymanEndpoint = this.hsCommonLaymanService.layman();
if (this.laymanEndpoint) {
await lastValueFrom(
this.hsLaymanBrowserService.queryCatalog(this.laymanEndpoint, {
@@ -851,10 +855,10 @@ export class HsDrawService extends HsDrawServiceParams {
*/
private async layerRemoval(multi: boolean = false) {
let confirmed;
- const a: ['map', 'mapcatalogue'] | ['map'] = this.hsCommonLaymanService
- .layman?.authenticated
- ? ['map', 'mapcatalogue']
- : ['map'];
+ const a: ['map', 'mapcatalogue'] | ['map'] =
+ this.hsCommonLaymanService.isAuthenticated()
+ ? ['map', 'mapcatalogue']
+ : ['map'];
if (multi) {
confirmed = await this.hsRemoveLayerDialogService.removeMultipleLayers(
this.drawableLayers,
diff --git a/projects/hslayers/services/endpoints/endpoints.service.ts b/projects/hslayers/services/endpoints/endpoints.service.ts
index 71048da725..1025e1b5b4 100644
--- a/projects/hslayers/services/endpoints/endpoints.service.ts
+++ b/projects/hslayers/services/endpoints/endpoints.service.ts
@@ -1,32 +1,25 @@
-import {Injectable} from '@angular/core';
+import {inject, Injectable} from '@angular/core';
-import {BehaviorSubject} from 'rxjs';
-
-import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
import {HsConfig} from 'hslayers-ng/config';
import {HsEndpoint} from 'hslayers-ng/types';
import {HsUtilsService} from 'hslayers-ng/services/utils';
+import {toSignal} from '@angular/core/rxjs-interop';
+import {of, switchMap} from 'rxjs';
@Injectable({providedIn: 'root'})
export class HsCommonEndpointsService {
- endpointsFilled: BehaviorSubject = new BehaviorSubject(null);
- endpoints: HsEndpoint[];
+ hsConfig = inject(HsConfig);
+ hsUtilsService = inject(HsUtilsService);
- constructor(
- public hsConfig: HsConfig,
- public hsCommonLaymanService: HsCommonLaymanService,
- public hsUtilsService: HsUtilsService,
- ) {
- this.fillEndpoints();
- this.hsConfig.configChanges.subscribe(() => {
- this.fillEndpoints();
- });
- }
+ endpoints = toSignal(
+ this.hsConfig.configChanges.pipe(switchMap(() => of(this.fillEndpoints()))),
+ {initialValue: [] as HsEndpoint[]},
+ );
- private fillEndpoints() {
- this.endpoints = [
- ...(this.hsConfig.datasources || []).map((ds) => {
- const tmp = {
+ private fillEndpoints(): HsEndpoint[] {
+ const endpoints = (this.hsConfig.datasources || []).map(
+ (ds) =>
+ ({
url: ds.url,
id: this.hsUtilsService.generateUuid(),
type: ds.type,
@@ -45,26 +38,17 @@ export class HsCommonEndpointsService {
paging: {
itemsPerPage: this.getItemsPerPageConfig(ds),
},
- user: undefined,
- getCurrentUserIfNeeded: async () =>
- await this.hsCommonLaymanService.getCurrentUserIfNeeded(tmp),
- };
- return tmp;
- }),
- ]
- /**
- * Sort endpoints in order to give layman's
- * layers priority in duplicate filtering.
- */
- .sort((a, b) => a.type.localeCompare(b.type));
+ }) as HsEndpoint,
+ );
- if (this.endpoints) {
- this.hsCommonLaymanService.layman$.next(
- this.endpoints.find((ep) => ep.type.includes('layman')),
- );
- this.endpointsFilled.next(this.endpoints);
- }
+ // Sort endpoints to give layman's layers priority in duplicate filtering
+ return endpoints.sort((a, b) => a.type.localeCompare(b.type));
}
+ /**
+ * Get items per page config
+ * @param endpoint - Endpoint
+ * @returns number
+ */
getItemsPerPageConfig(endpoint): number {
return endpoint.paging !== undefined &&
endpoint.paging.itemsPerPage !== undefined
diff --git a/projects/hslayers/services/get-capabilities/wms-get-capabilities.service.ts b/projects/hslayers/services/get-capabilities/wms-get-capabilities.service.ts
index e5fe80bb1b..90907472af 100644
--- a/projects/hslayers/services/get-capabilities/wms-get-capabilities.service.ts
+++ b/projects/hslayers/services/get-capabilities/wms-get-capabilities.service.ts
@@ -109,7 +109,7 @@ export class HsWmsGetCapabilitiesService implements IGetCapabilities {
responseType: 'text',
withCredentials: isLaymanUrl(
url,
- this.hsCommonLaymanService.layman,
+ this.hsCommonLaymanService.layman(),
),
observe: 'response', // Set observe to 'response' to get headers as well
})
diff --git a/projects/hslayers/services/map/map.service.ts b/projects/hslayers/services/map/map.service.ts
index b86668875d..c972314e10 100644
--- a/projects/hslayers/services/map/map.service.ts
+++ b/projects/hslayers/services/map/map.service.ts
@@ -1041,7 +1041,7 @@ export class HsMapService {
(image.getImage() as HTMLImageElement).src = src;
return;
}
- const laymanEp = this.hsCommonLaymanService.layman;
+ const laymanEp = this.hsCommonLaymanService.layman();
if (laymanEp && src.startsWith(laymanEp.url)) {
this.laymanWmsLoadingFunction(image, src)
.then((_) => {
diff --git a/projects/hslayers/services/save-map/layer-synchronizer.service.ts b/projects/hslayers/services/save-map/layer-synchronizer.service.ts
index 2e0f89f37b..ffff6e0d67 100644
--- a/projects/hslayers/services/save-map/layer-synchronizer.service.ts
+++ b/projects/hslayers/services/save-map/layer-synchronizer.service.ts
@@ -58,13 +58,17 @@ export class HsLayerSynchronizerService {
element: lyr,
});
});
- this.hsCommonLaymanService.authChange.subscribe(() => {
- this.reloadLayersOnAuthChange();
- });
+
this.crs = this.hsMapService.getCurrentProj().getCode();
this.hsLaymanService.crs = this.crs;
});
+ this.hsCommonLaymanService.layman$
+ .pipe(takeUntilDestroyed())
+ .subscribe((layman) => {
+ this.reloadLayersOnAuthChange();
+ });
+
this.hsEventBusService.refreshLaymanLayer
.pipe(takeUntilDestroyed())
.subscribe((layer) => {
@@ -75,11 +79,13 @@ export class HsLayerSynchronizerService {
* Reload all the synchronized layers after Layman's authorization change
*/
private reloadLayersOnAuthChange(): void {
- for (const layer of this.syncedLayers) {
- const layerSource = layer.getSource();
- setLaymanLayerDescriptor(layer, undefined);
- layerSource.clear();
- this.pull(layer, layerSource);
+ if (this.syncedLayers.length > 0) {
+ for (const layer of this.syncedLayers) {
+ const layerSource = layer.getSource();
+ setLaymanLayerDescriptor(layer, undefined);
+ layerSource.clear();
+ this.pull(layer, layerSource);
+ }
}
}
@@ -123,7 +129,7 @@ export class HsLayerSynchronizerService {
if (!desc) {
if (retryCount < maxRetryCount) {
const desc = await this.hsLaymanService.describeLayer(
- this.hsCommonLaymanService.layman,
+ this.hsCommonLaymanService.layman(),
getName(layer),
getWorkspace(layer),
);
@@ -180,7 +186,7 @@ export class HsLayerSynchronizerService {
*/
findLaymanForWfsLayer(layer: VectorLayer>) {
const definitionUrl = getDefinition(layer).url;
- const laymanEp = this.hsCommonLaymanService?.layman;
+ const laymanEp = this.hsCommonLaymanService?.layman();
if (!laymanEp || !definitionUrl) {
return undefined;
}
diff --git a/projects/hslayers/services/save-map/layman.service.ts b/projects/hslayers/services/save-map/layman.service.ts
index 7f7e6a159e..0b8d3fa3bf 100644
--- a/projects/hslayers/services/save-map/layman.service.ts
+++ b/projects/hslayers/services/save-map/layman.service.ts
@@ -1,5 +1,5 @@
import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {Injectable} from '@angular/core';
+import {computed, Injectable} from '@angular/core';
import Resumable from 'resumablejs';
import {
@@ -21,7 +21,6 @@ import {Layer, Vector as VectorLayer} from 'ol/layer';
import {Source, Vector as VectorSource} from 'ol/source';
import {
- AboutLayman,
AccessRightsModel,
AsyncUpload,
CompoData,
@@ -45,13 +44,11 @@ import {
wfsNotAvailable,
PostPatchLayerResponse,
} from 'hslayers-ng/common/layman';
-import {HsCommonEndpointsService} from 'hslayers-ng/services/endpoints';
import {HsLanguageService} from 'hslayers-ng/services/language';
import {HsLogService} from 'hslayers-ng/services/log';
import {HsMapService} from 'hslayers-ng/services/map';
import {HsSaverService} from './saver-service.interface';
import {HsToastService} from 'hslayers-ng/common/toast';
-import {HsUtilsService} from 'hslayers-ng/services/utils';
import {
createGetFeatureRequest,
createPostFeatureRequest,
@@ -76,65 +73,23 @@ export class HsLaymanService implements HsSaverService {
laymanLayerPending: Subject = new Subject();
totalProgress = 0;
deleteQuery: Subscription;
- supportedCRRList: string[] = SUPPORTED_SRS_LIST;
+ supportedCRRList = computed(() => {
+ const laymanEP = this.hsCommonLaymanService.layman();
+ if (laymanEP) {
+ return getSupportedSrsList(laymanEP);
+ }
+ return SUPPORTED_SRS_LIST;
+ });
pendingRequests: Map> = new Map();
constructor(
- private hsUtilsService: HsUtilsService,
private http: HttpClient,
private hsMapService: HsMapService,
private hsLogService: HsLogService,
- private hsCommonEndpointsService: HsCommonEndpointsService,
private hsToastService: HsToastService,
private hsLanguageService: HsLanguageService,
private hsCommonLaymanService: HsCommonLaymanService,
- ) {
- this.hsCommonEndpointsService.endpointsFilled.subscribe(
- async (endpoints) => {
- if (endpoints) {
- const laymanEP = endpoints.find((ep) => ep.type.includes('layman'));
- if (laymanEP) {
- const laymanVersion: AboutLayman = await lastValueFrom(
- this.http
- .get(laymanEP.url + '/rest/about/version')
- .pipe(
- map((res: any) => {
- return {
- about: {
- applications: {
- layman: {
- version: res.about.applications.layman.version,
- releaseTimestamp:
- res.about.applications.layman[
- 'release-timestamp'
- ],
- },
- laymanTestClient: {
- version:
- res.about.applications['layman-test-client']
- .version,
- },
- },
- data: {
- layman: {
- lastSchemaMigration:
- res.about.applications['last-schema-migration'],
- lastDataMigration:
- res.about.applications['last-data-migration'],
- },
- },
- },
- };
- }),
- ),
- );
- laymanEP.version = laymanVersion.about.applications.layman.version;
- this.supportedCRRList = getSupportedSrsList(laymanEP);
- }
- }
- },
- );
- }
+ ) {}
/**
* Update composition's access rights
@@ -148,14 +103,14 @@ export class HsLaymanService implements HsSaverService {
endpoint: HsEndpoint,
access_rights: AccessRightsModel,
): Promise {
- const rights = this.parseAccessRightsForLayman(endpoint, access_rights);
+ const rights = this.parseAccessRightsForLayman(access_rights);
const formdata = new FormData();
formdata.append('name', compName);
formdata.append('access_rights.read', rights.read);
formdata.append('access_rights.write', rights.write);
return await this.makeMapPostPatchRequest(
endpoint,
- endpoint.user,
+ this.hsCommonLaymanService.user(),
compName,
formdata,
false,
@@ -175,10 +130,7 @@ export class HsLaymanService implements HsSaverService {
compoData: CompoData,
saveAsNew: boolean,
): Promise {
- const rights = this.parseAccessRightsForLayman(
- endpoint,
- compoData.access_rights,
- );
+ const rights = this.parseAccessRightsForLayman(compoData.access_rights);
const formdata = new FormData();
formdata.append(
'file',
@@ -192,11 +144,12 @@ export class HsLaymanService implements HsSaverService {
formdata.append('access_rights.read', rights.read);
formdata.append('access_rights.write', rights.write);
+ const user = this.hsCommonLaymanService.user();
const workspace =
- compoData.workspace === endpoint.user
- ? endpoint.user
+ compoData.workspace === user
+ ? user
: saveAsNew
- ? endpoint.user
+ ? user
: compoData.workspace;
return await this.makeMapPostPatchRequest(
@@ -215,20 +168,18 @@ export class HsLaymanService implements HsSaverService {
* @param access_rights - Provided access rights
* @returns Access rights object as two strings, one for read access and the other for write access
*/
- parseAccessRightsForLayman(
- endpoint: HsEndpoint,
- access_rights: AccessRightsModel,
- ): {
+ parseAccessRightsForLayman(access_rights: AccessRightsModel): {
write: string;
read: string;
} {
+ const user = this.hsCommonLaymanService.user();
const write =
access_rights['access_rights.write'] == 'private'
- ? endpoint.user
+ ? user
: access_rights['access_rights.write'];
const read =
access_rights['access_rights.read'] == 'private'
- ? endpoint.user
+ ? user
: access_rights['access_rights.read'];
return {write, read};
}
@@ -346,10 +297,7 @@ export class HsLaymanService implements HsSaverService {
formData.append('crs', description.crs);
}
if (description.access_rights) {
- const rights = this.parseAccessRightsForLayman(
- endpoint,
- description.access_rights,
- );
+ const rights = this.parseAccessRightsForLayman(description.access_rights);
formData.append('access_rights.write', rights.write);
formData.append('access_rights.read', rights.read);
@@ -406,7 +354,8 @@ export class HsLaymanService implements HsSaverService {
layerName = getLaymanFriendlyLayerName(layerName);
try {
const postOrPatch = overwrite ? 'patch' : 'post';
- const url = `${endpoint.url}/rest/workspaces/${endpoint.user}/layers${
+ const workspace = this.hsCommonLaymanService.user();
+ const url = `${endpoint.url}/rest/workspaces/${workspace}/layers${
overwrite ? `/${layerName}` : `?${Math.random()}`
}`;
let data: PostPatchLayerResponse = await lastValueFrom(
@@ -503,8 +452,9 @@ export class HsLaymanService implements HsSaverService {
),
);
const layername = data['name'];
+ const workspace = this.hsCommonLaymanService.user();
const resumable = new Resumable({
- target: `${endpoint.url}/rest/workspaces/${endpoint.user}/layers/${layername}/chunk`,
+ target: `${endpoint.url}/rest/workspaces/${workspace}/layers/${layername}/chunk`,
query: {
'layman_original_parameter': 'file',
},
@@ -559,7 +509,7 @@ export class HsLaymanService implements HsSaverService {
}
const layerName = getLayerName(layer);
let layerTitle = getTitle(layer);
- const crsSupported = this.supportedCRRList.includes(this.crs);
+ const crsSupported = this.supportedCRRList().includes(this.crs);
if ((endpoint?.version?.split('.').join() as unknown as number) < 171) {
layerTitle = getLaymanFriendlyLayerName(layerTitle);
@@ -641,7 +591,7 @@ export class HsLaymanService implements HsSaverService {
try {
if (!desc) {
desc = await this.describeLayer(endpoint, name, getWorkspace(layer));
- this.cacheLaymanDescriptor(layer, desc, endpoint);
+ this.cacheLaymanDescriptor(layer, desc);
}
if (desc.name == undefined || desc.wfs.url == undefined) {
throw `Layer or its name/url didn't exist`;
@@ -679,11 +629,12 @@ export class HsLaymanService implements HsSaverService {
try {
const srsName = this.hsMapService.getCurrentProj().getCode();
const featureType = getLayerName(layer);
+ const user = this.hsCommonLaymanService.user();
const {default: WFS} = await import('ol/format/WFS');
const wfsFormat = new WFS();
const options = {
- featureNS: 'http://' + ep.user,
- featurePrefix: ep.user,
+ featureNS: 'http://' + user,
+ featurePrefix: user,
featureType,
srsName,
nativeElements: null,
@@ -718,14 +669,12 @@ export class HsLaymanService implements HsSaverService {
* Cache Layman's layer descriptor so it can be used later on
* @param layer - Layer interacted with
* @param layer - Layman's layer descriptor
- * @param endpoint - Layman's endpoint description
*/
private cacheLaymanDescriptor(
layer: VectorLayer>,
desc: HsLaymanLayerDescriptor,
- endpoint: HsEndpoint,
): void {
- if (endpoint.user != 'browser') {
+ if (this.hsCommonLaymanService.user() != 'browser') {
setLaymanLayerDescriptor(layer, desc);
}
}
@@ -756,7 +705,7 @@ export class HsLaymanService implements HsSaverService {
return null;
}
if (desc?.name && !wfsNotAvailable(desc)) {
- this.cacheLaymanDescriptor(layer, desc, endpoint);
+ this.cacheLaymanDescriptor(layer, desc);
}
} catch (ex) {
//If Layman returned 404
@@ -948,14 +897,15 @@ export class HsLaymanService implements HsSaverService {
}
const observables: Observable[] = [];
- const ds = this.hsCommonLaymanService.layman;
+ const ds = this.hsCommonLaymanService.layman();
+ const workspace = this.hsCommonLaymanService.user();
let url;
if (layer) {
const layerName =
typeof layer == 'string' ? layer : getLayerName(layer);
- url = `${ds.url}/rest/workspaces/${ds.user}/layers/${layerName}`;
+ url = `${ds.url}/rest/workspaces/${workspace}/layers/${layerName}`;
} else {
- url = `${ds.url}/rest/workspaces/${ds.user}/layers`;
+ url = `${ds.url}/rest/workspaces/${workspace}/layers`;
}
const response = this.http
diff --git a/projects/hslayers/services/styler/styler.service.ts b/projects/hslayers/services/styler/styler.service.ts
index fc13baf87d..f43783dd53 100644
--- a/projects/hslayers/services/styler/styler.service.ts
+++ b/projects/hslayers/services/styler/styler.service.ts
@@ -65,7 +65,7 @@ export class HsStylerService {
sld: string;
qml: string;
- isAuthenticated: boolean;
+ isAuthenticated = this.hsCommonLaymanService.isAuthenticated;
pin_white_blue;
pin_white_blue_highlight;
@@ -120,10 +120,6 @@ export class HsStylerService {
];
};
- this.hsCommonLaymanService.authChange.subscribe((endpoint) => {
- this.isAuthenticated = endpoint?.authenticated;
- });
-
this.hsMapService.loaded().then((map) => {
for (const layer of this.hsMapService
.getLayersArray()
@@ -755,7 +751,7 @@ export class HsStylerService {
* Indicate only when user is logged in Layman and layer is being monitored otherwise save
*/
resolveSldChange() {
- if (this.isAuthenticated && this.layerBeingMonitored) {
+ if (this.isAuthenticated() && this.layerBeingMonitored) {
this.changesStore.set(getUid(this.layer), {
sld: this.sld,
qml: this.qml,
diff --git a/projects/hslayers/services/utils/utils.service.ts b/projects/hslayers/services/utils/utils.service.ts
index e0ea884157..b460b9cc9d 100644
--- a/projects/hslayers/services/utils/utils.service.ts
+++ b/projects/hslayers/services/utils/utils.service.ts
@@ -1,5 +1,5 @@
import {HttpClient} from '@angular/common/http';
-import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
+import {Inject, Injectable, Injector, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
import {LineString, Polygon} from 'ol/geom';
@@ -7,8 +7,7 @@ import {ProjectionLike, get as getProjection, transform} from 'ol/proj';
import {getArea, getDistance} from 'ol/sphere';
import {lastValueFrom} from 'rxjs';
-import {BoundingBoxObject} from 'hslayers-ng/types';
-import {HsCommonLaymanService} from 'hslayers-ng/common/layman';
+import {BoundingBoxObject, HsEndpoint} from 'hslayers-ng/types';
import {HsConfig} from 'hslayers-ng/config';
import {HsLogService} from 'hslayers-ng/services/log';
@@ -22,30 +21,45 @@ export type Measurement = {
providedIn: 'root',
})
export class HsUtilsService {
+ private laymanUrl: string;
+
constructor(
public hsConfig: HsConfig,
private http: HttpClient,
private LogService: HsLogService,
- private hsCommonLaymanService: HsCommonLaymanService,
+ private injector: Injector,
@Inject(PLATFORM_ID) private platformId: any,
) {}
+ /**
+ * Register Layman endpoints to avoid proxifying them
+ * @param endpoints - Layman endpoints to register
+ */
+ registerLaymanEndpoints(url: string): void {
+ this.laymanUrl = url;
+ }
+
/**
* Proxify URL if enabled.
* @param url - URL to proxify
* @returns Encoded URL with path to hslayers-server proxy
*/
proxify(url: string): string {
- const laymanEp = this.hsCommonLaymanService.layman;
- if (
- url.startsWith(this.hsConfig.proxyPrefix) ||
- (laymanEp && url.startsWith(laymanEp.url))
- ) {
+ // Don't proxify if it's already proxified
+ if (url.startsWith(this.hsConfig.proxyPrefix)) {
return url;
}
+
+ // Don't proxify data URLs
if (url.startsWith('data:application')) {
return url;
}
+
+ // Don't proxify Layman endpoints
+ if (url.startsWith(this.laymanUrl)) {
+ return url;
+ }
+
let outUrl = url;
//Not using location because don't know if port 80 was specified explicitly or not
const windowUrlPosition = url.indexOf(window.location.origin);
diff --git a/projects/hslayers/types/authentication.ts b/projects/hslayers/types/authentication.ts
new file mode 100644
index 0000000000..1dfae2cfd1
--- /dev/null
+++ b/projects/hslayers/types/authentication.ts
@@ -0,0 +1,8 @@
+export interface AuthState {
+ user?: string;
+ authenticated?: boolean;
+}
+
+export interface AuthAction {
+ type: 'login' | 'logout';
+}
diff --git a/projects/hslayers/types/endpoint.interface.ts b/projects/hslayers/types/endpoint.interface.ts
index d003046519..13793a27c7 100644
--- a/projects/hslayers/types/endpoint.interface.ts
+++ b/projects/hslayers/types/endpoint.interface.ts
@@ -20,8 +20,6 @@ export interface HsEndpoint {
language?;
listLoading?;
layers?: HsAddDataLayerDescriptor[];
- user?: string;
- authenticated?: boolean;
code_list_url?: string;
code_lists?;
version?: string;
@@ -53,7 +51,6 @@ export interface HsEndpoint {
compositionLoad?: EndpointErrorHandling | EndpointErrorHandler;
addDataCatalogueLoad?: EndpointErrorHandling | EndpointErrorHandler;
};
- getCurrentUserIfNeeded?(endpoint: HsEndpoint): Promise;
}
function isErrorHandlerFunction(object: any): object is EndpointErrorHandler {