oip-common 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, InjectionToken, signal, computed, effect, DestroyRef, ChangeDetectorRef, Component, Input, PLATFORM_ID, HostBinding, EventEmitter, Output, ViewChild, Renderer2, SecurityContext, ChangeDetectionStrategy, makeEnvironmentProviders, Pipe } from '@angular/core';
2
+ import { Injectable, inject, signal, computed, effect, DestroyRef, ChangeDetectorRef, Component, Input, InjectionToken, PLATFORM_ID, ViewChild, HostBinding, EventEmitter, Output, Renderer2, SecurityContext, ChangeDetectionStrategy, makeEnvironmentProviders, importProvidersFrom } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
- import * as i2$6 from 'primeng/api';
4
+ import * as i2$3 from 'primeng/api';
5
5
  import { MessageService, ConfirmationService, PrimeIcons, SharedModule } from 'primeng/api';
6
- import { HttpErrorResponse, HttpClient as HttpClient$1, HttpHeaders } from '@angular/common/http';
6
+ import { HttpErrorResponse, HttpClient as HttpClient$1, provideHttpClient, withInterceptors, withFetch } from '@angular/common/http';
7
7
  import * as i2$4 from '@ngx-translate/core';
8
- import { TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
9
- import * as i1$3 from '@angular/router';
8
+ import { TranslateService, TranslatePipe, TranslateModule, TranslateLoader } from '@ngx-translate/core';
9
+ import * as i1$4 from '@angular/router';
10
10
  import { ActivatedRoute, Router, RouterModule, NavigationEnd, RouterLinkActive, RouterLink } from '@angular/router';
11
- import { lastValueFrom, BehaviorSubject, Subject, ReplaySubject, merge, from, filter as filter$1, combineLatest, of, map as map$1, Observable } from 'rxjs';
12
- import { filter, distinctUntilChanged, map, switchMap, catchError } from 'rxjs/operators';
11
+ import { BehaviorSubject, Subject, of, tap, shareReplay, ReplaySubject, firstValueFrom, merge, from, finalize, filter as filter$1 } from 'rxjs';
12
+ import { tap as tap$1, catchError, filter, distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
13
13
  import { Title, DomSanitizer } from '@angular/platform-browser';
14
14
  import { PrimeNG } from 'primeng/config';
15
- import { OidcSecurityService, PublicEventsService, EventTypes, StsConfigHttpLoader } from 'angular-auth-oidc-client';
15
+ import { OidcSecurityService, PublicEventsService, EventTypes } from 'angular-auth-oidc-client';
16
16
  import * as i1 from 'primeng/multiselect';
17
17
  import { MultiSelectModule } from 'primeng/multiselect';
18
18
  import * as i2 from 'primeng/tooltip';
@@ -22,8 +22,8 @@ import { ButtonModule, Button } from 'primeng/button';
22
22
  import * as i1$1 from '@angular/forms';
23
23
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
24
24
  import * as i2$1 from '@angular/common';
25
- import { isPlatformBrowser, CommonModule, NgComponentOutlet, NgClass, DatePipe } from '@angular/common';
26
- import * as i3$1 from 'primeng/styleclass';
25
+ import { isPlatformBrowser, CommonModule, NgComponentOutlet, NgClass, DatePipe, PathLocationStrategy, LocationStrategy } from '@angular/common';
26
+ import * as i3$2 from 'primeng/styleclass';
27
27
  import { StyleClassModule } from 'primeng/styleclass';
28
28
  import { updatePreset, updateSurfacePalette, $t } from '@primeng/themes';
29
29
  import Aura from '@primeng/themes/aura';
@@ -34,40 +34,44 @@ import { SelectButtonModule } from 'primeng/selectbutton';
34
34
  import { Tabs, TabList, Tab } from 'primeng/tabs';
35
35
  import * as i2$2 from 'primeng/avatar';
36
36
  import { AvatarModule } from 'primeng/avatar';
37
- import * as i1$4 from 'primeng/contextmenu';
37
+ import { ConfirmDialog } from 'primeng/confirmdialog';
38
+ import * as i1$3 from 'primeng/badge';
39
+ import { BadgeModule } from 'primeng/badge';
40
+ import * as i3$1 from 'primeng/paginator';
41
+ import { PaginatorModule } from 'primeng/paginator';
42
+ import * as i7 from 'primeng/popover';
43
+ import { Popover, PopoverModule } from 'primeng/popover';
44
+ import * as i4 from 'primeng/progressspinner';
45
+ import { ProgressSpinnerModule } from 'primeng/progressspinner';
46
+ import * as i9 from 'primeng/tag';
47
+ import { Tag, TagModule } from 'primeng/tag';
48
+ import * as signalR from '@microsoft/signalr';
49
+ import * as i1$5 from 'primeng/contextmenu';
38
50
  import { ContextMenuModule, ContextMenu } from 'primeng/contextmenu';
39
- import * as i2$3 from 'primeng/dialog';
51
+ import * as i3$4 from 'primeng/dialog';
40
52
  import { DialogModule } from 'primeng/dialog';
41
- import * as i3$3 from 'primeng/inputtext';
53
+ import * as i4$1 from 'primeng/inputtext';
42
54
  import { InputTextModule, InputText } from 'primeng/inputtext';
43
- import { trigger, state, transition, style, animate } from '@angular/animations';
44
- import * as i3$2 from 'primeng/ripple';
55
+ import * as i3$3 from 'primeng/ripple';
45
56
  import { RippleModule } from 'primeng/ripple';
46
- import { ConfirmDialog } from 'primeng/confirmdialog';
47
- import * as i4 from 'primeng/select';
57
+ import * as i5 from 'primeng/select';
48
58
  import { SelectModule, Select } from 'primeng/select';
49
- import * as i1$5 from 'primeng/fileupload';
59
+ import * as i1$6 from 'primeng/fileupload';
50
60
  import { FileUploadModule } from 'primeng/fileupload';
51
61
  import { ImageModule } from 'primeng/image';
52
- import * as i1$6 from 'primeng/table';
62
+ import * as i1$7 from 'primeng/table';
53
63
  import { TableModule } from 'primeng/table';
54
64
  import * as i2$5 from 'primeng/toggleswitch';
55
65
  import { ToggleSwitchModule } from 'primeng/toggleswitch';
56
- import * as i9 from 'primeng/tag';
57
- import { TagModule, Tag } from 'primeng/tag';
58
66
  import { TextareaModule, Textarea } from 'primeng/textarea';
59
- import * as i4$1 from 'primeng/toolbar';
67
+ import * as i4$2 from 'primeng/toolbar';
60
68
  import { ToolbarModule } from 'primeng/toolbar';
61
- import * as i5 from 'primeng/card';
69
+ import * as i5$1 from 'primeng/card';
62
70
  import { CardModule } from 'primeng/card';
63
71
  import { DividerModule } from 'primeng/divider';
64
72
  import * as i6 from 'primeng/panel';
65
73
  import { PanelModule } from 'primeng/panel';
66
- import * as i7 from 'primeng/popover';
67
- import { PopoverModule } from 'primeng/popover';
68
- import * as i8 from 'primeng/progressspinner';
69
- import { ProgressSpinnerModule } from 'primeng/progressspinner';
70
- import * as signalR from '@microsoft/signalr';
74
+ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
71
75
 
72
76
  class TopBarService {
73
77
  get availableTopBarItems() {
@@ -235,94 +239,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
235
239
  args: [{ providedIn: 'root' }]
236
240
  }] });
237
241
 
238
- const DEFAULT_OIP_FRONTEND_CONFIG = {
239
- appMode: 'standalone',
240
- apiBaseUrl: '',
241
- notificationHubUrl: ''
242
- };
243
- const OIP_FRONTEND_CONFIG = new InjectionToken('OIP_FRONTEND_CONFIG', {
244
- factory: () => DEFAULT_OIP_FRONTEND_CONFIG
245
- });
246
-
247
- /**
248
- * BaseDataService provides a unified interface for sending HTTP requests
249
- * using Angular's HttpClient. It supports standard HTTP methods and automatic
250
- * credential handling.
251
- */
252
- class BaseDataService {
253
- constructor() {
254
- this.http = inject(HttpClient$1);
255
- this.frontendConfig = inject(OIP_FRONTEND_CONFIG);
256
- }
257
- /**
258
- * Gets the base URL of the application from the HTML <base> tag.
259
- */
260
- get baseUrl() {
261
- return this.normalizeBaseUrl(this.frontendConfig.apiBaseUrl || document.getElementsByTagName('base')[0].href);
262
- }
263
- /**
264
- * Builds a fully-qualified application URL based on frontend mode.
265
- */
266
- buildUrl(path) {
267
- const normalizedPath = path.startsWith('/') ? path.substring(1) : path;
268
- return `${this.baseUrl}${normalizedPath}`;
269
- }
270
- /**
271
- * Sends an HTTP request with the specified method and data.
272
- *
273
- * @template TResponse - Expected response type.
274
- * @param url - The target URL for the HTTP request.
275
- * @param method - The HTTP method to use (GET, PUT, POST, DELETE). Default is 'GET'.
276
- * @param data - An object containing request parameters or payload.
277
- * @returns A promise that resolves to the response of type TResponse.
278
- */
279
- sendRequest(url, method = 'GET', data = {}) {
280
- const httpOptions = { withCredentials: true };
281
- let result;
282
- switch (method) {
283
- case 'GET':
284
- result = this.http.get(url, { params: data });
285
- break;
286
- case 'PUT':
287
- result = this.http.put(url, data, httpOptions);
288
- break;
289
- case 'POST':
290
- result = this.http.post(url, data, httpOptions);
291
- break;
292
- case 'DELETE':
293
- result = this.http.request('DELETE', url, {
294
- body: data,
295
- headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
296
- withCredentials: true
297
- });
298
- break;
299
- }
300
- return lastValueFrom(result);
301
- }
302
- /**
303
- * Sends a GET request and retrieves a response as a Blob.
304
- *
305
- * @param url - The target URL for the GET request.
306
- * @returns A promise that resolves to a Object response.
307
- */
308
- getBlob(url) {
309
- const httpOptions = {
310
- responseType: 'blob',
311
- withCredentials: true
312
- };
313
- const result = this.http.get(url, httpOptions);
314
- return lastValueFrom(result);
315
- }
316
- normalizeBaseUrl(baseUrl) {
317
- return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
318
- }
319
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BaseDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
320
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BaseDataService }); }
321
- }
322
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BaseDataService, decorators: [{
323
- type: Injectable
324
- }] });
325
-
326
242
  /**
327
243
  * Service to manage the application title.
328
244
  */
@@ -611,6 +527,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
611
527
  class L10nService {
612
528
  constructor() {
613
529
  this.loadedTranslations = new Set();
530
+ this.loadingTranslations = new Map();
614
531
  this.httpClient = inject(HttpClient$1);
615
532
  this.translateService = inject(TranslateService);
616
533
  this.primeNg = inject(PrimeNG);
@@ -621,8 +538,8 @@ class L10nService {
621
538
  * @param component - Name of the component to load translations for
622
539
  */
623
540
  loadComponentTranslations(component) {
624
- const lang = this.translateService.currentLang;
625
- this.loadTranslations(component, lang);
541
+ const lang = this.translateService.currentLang || this.layoutService.language() || 'en';
542
+ return this.loadTranslations(component, lang);
626
543
  }
627
544
  /**
628
545
  * Gets the translated value of a key (or an array of keys)
@@ -640,19 +557,27 @@ class L10nService {
640
557
  loadTranslations(component, lang) {
641
558
  const key = `${component}.${lang}`;
642
559
  if (this.loadedTranslations.has(key)) {
643
- return;
644
- }
645
- try {
646
- this.httpClient.get(`./assets/i18n/${component}.${lang}.json`).subscribe((translations) => {
647
- const current = this.translateService.translations[lang] || {};
648
- this.translateService.setTranslation(lang, { ...current, ...translations }, true);
649
- this.loadedTranslations.add(key);
650
- });
560
+ return of(null);
651
561
  }
652
- catch (e) {
653
- console.error(`No translations found for ${component}.${lang}.json`);
654
- console.error(e);
562
+ const loading = this.loadingTranslations.get(key);
563
+ if (loading) {
564
+ return loading;
655
565
  }
566
+ const request = this.httpClient.get(`./assets/i18n/${component}.${lang}.json`).pipe(tap((translations) => {
567
+ const current = this.translateService.translations[lang] || {};
568
+ this.translateService.setTranslation(lang, { ...current, ...translations }, true);
569
+ this.loadedTranslations.add(key);
570
+ this.loadingTranslations.delete(key);
571
+ }), shareReplay(1));
572
+ this.loadingTranslations.set(key, request);
573
+ request.subscribe({
574
+ error: (e) => {
575
+ this.loadingTranslations.delete(key);
576
+ console.error(`No translations found for ${component}.${lang}.json`);
577
+ console.error(e);
578
+ }
579
+ });
580
+ return request;
656
581
  }
657
582
  /**
658
583
  * Changes the lang currently used
@@ -684,25 +609,131 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
684
609
  args: [{ providedIn: 'root' }]
685
610
  }] });
686
611
 
687
- class SecurityDataService extends BaseDataService {
688
- getSecurity(controller, id) {
689
- return this.sendRequest(this.baseUrl + `api/${controller}/get-security?id=${id}`);
612
+ class SecurityService {
613
+ }
614
+ class BffSecurityService {
615
+ constructor() {
616
+ this.http = inject(HttpClient$1);
617
+ this.authenticated = new BehaviorSubject(null);
618
+ this.currentUser = new BehaviorSubject(null);
619
+ this.csrfToken = new ReplaySubject(1);
620
+ this.payload = new BehaviorSubject(null);
690
621
  }
691
- saveSecurity(controller, request) {
692
- return this.sendRequest(this.baseUrl + `api/${controller}/put-security`, 'PUT', request);
622
+ auth() {
623
+ this.http.get(this.buildUrl('api/security/get-current-auth-session'), {
624
+ withCredentials: true
625
+ }).pipe(tap$1((session) => this.applySession(session)), catchError(() => {
626
+ this.applyAnonymousSession();
627
+ return of(null);
628
+ })).subscribe();
629
+ }
630
+ logout() {
631
+ firstValueFrom(this.getCsrfToken()).then((csrfToken) => {
632
+ const form = this.createPostForm(this.buildUrl('api/security/delete-auth-session'));
633
+ if (csrfToken?.token) {
634
+ const tokenInput = document.createElement('input');
635
+ tokenInput.type = 'hidden';
636
+ tokenInput.name = '__RequestVerificationToken';
637
+ tokenInput.value = csrfToken.token;
638
+ form.appendChild(tokenInput);
639
+ }
640
+ document.body.appendChild(form);
641
+ form.submit();
642
+ });
643
+ }
644
+ isAuthenticated() {
645
+ return this.authenticated.asObservable().pipe(filter((value) => value !== null), distinctUntilChanged());
646
+ }
647
+ getAccessToken() {
648
+ return of('');
649
+ }
650
+ isTokenExpired() {
651
+ return of(false);
693
652
  }
694
- getRealmRoles() {
695
- return this.sendRequest(this.baseUrl + `api/security/get-realm-roles`);
653
+ getCurrentUser() {
654
+ return this.currentUser.getValue();
655
+ }
656
+ getCurrentUser$() {
657
+ return this.currentUser.asObservable();
658
+ }
659
+ forceRefreshSession() {
660
+ this.auth();
661
+ return of({
662
+ isAuthenticated: this.authenticated.getValue() === true,
663
+ userData: this.currentUser.getValue()
664
+ });
665
+ }
666
+ getCsrfToken() {
667
+ this.http.get(this.buildUrl('api/security/get-auth-csrf-token'), {
668
+ withCredentials: true
669
+ }).pipe(catchError(() => of(null))).subscribe((token) => this.csrfToken.next(token));
670
+ return this.csrfToken.asObservable();
671
+ }
672
+ isAdmin() {
673
+ return this.payload.getValue()?.realm_access?.roles?.includes('admin') ?? false;
674
+ }
675
+ authorize() {
676
+ const form = this.createPostForm(this.buildUrl('api/security/create-auth-session'));
677
+ document.body.appendChild(form);
678
+ form.submit();
679
+ }
680
+ ngOnDestroy() {
681
+ this.authenticated.complete();
682
+ this.currentUser.complete();
683
+ this.payload.complete();
696
684
  }
697
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
698
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService }); }
685
+ applySession(session) {
686
+ const roles = session.roles ?? [];
687
+ const user = this.createCurrentUser(session, roles);
688
+ this.authenticated.next(session.isAuthenticated);
689
+ this.currentUser.next(user);
690
+ this.payload.next({ realm_access: { roles }, ...user });
691
+ }
692
+ createCurrentUser(session, roles) {
693
+ const displayName = session.displayName || session.userName || session.email;
694
+ const nameParts = this.splitDisplayName(displayName);
695
+ return {
696
+ userName: session.userName,
697
+ displayName,
698
+ email: session.email,
699
+ roles,
700
+ preferred_username: session.userName,
701
+ name: displayName,
702
+ given_name: nameParts.givenName,
703
+ family_name: nameParts.familyName
704
+ };
705
+ }
706
+ splitDisplayName(displayName) {
707
+ const parts = displayName?.trim().split(/\s+/).filter(Boolean) ?? [];
708
+ return {
709
+ givenName: parts[0],
710
+ familyName: parts.length > 1 ? parts.slice(1).join(' ') : undefined
711
+ };
712
+ }
713
+ applyAnonymousSession() {
714
+ this.authenticated.next(false);
715
+ this.currentUser.next(null);
716
+ this.payload.next(null);
717
+ this.csrfToken.next(null);
718
+ }
719
+ createPostForm(action) {
720
+ const form = document.createElement('form');
721
+ form.method = 'post';
722
+ form.action = action;
723
+ form.style.display = 'none';
724
+ return form;
725
+ }
726
+ buildUrl(path) {
727
+ const baseUrl = document.getElementsByTagName('base')[0].href;
728
+ const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
729
+ return `${normalizedBase}${path}`;
730
+ }
731
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BffSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
732
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BffSecurityService }); }
699
733
  }
700
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService, decorators: [{
734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BffSecurityService, decorators: [{
701
735
  type: Injectable
702
736
  }] });
703
-
704
- class SecurityService {
705
- }
706
737
  /**
707
738
  * SecurityService extends OidcSecurityService to manage authentication,
708
739
  * token handling, and user role access in an Angular application.
@@ -717,6 +748,11 @@ class KeycloakSecurityService extends OidcSecurityService {
717
748
  */
718
749
  constructor() {
719
750
  super();
751
+ this.refreshLockKeyPrefix = 'oip:keycloak-refresh-lock';
752
+ this.refreshResultKeyPrefix = 'oip:keycloak-refresh-result';
753
+ this.refreshLockTtlMs = 15000;
754
+ this.refreshWaitTimeoutMs = 10000;
755
+ this.refreshTabId = this.createRefreshTabId();
720
756
  /**
721
757
  * Handles angular OIDC events.
722
758
  */
@@ -742,7 +778,9 @@ class KeycloakSecurityService extends OidcSecurityService {
742
778
  .registerForEvents()
743
779
  .pipe(filter((event) => event.type === EventTypes.NewAuthenticationResult))
744
780
  .subscribe(() => {
745
- super.getAccessToken().subscribe(token => { this.accessToken.next(token); });
781
+ super.getAccessToken().subscribe(token => {
782
+ this.accessToken.next(token);
783
+ });
746
784
  this.auth();
747
785
  });
748
786
  }
@@ -759,6 +797,17 @@ class KeycloakSecurityService extends OidcSecurityService {
759
797
  getAccessToken(configId) {
760
798
  return merge(super.getAccessToken(configId), this.accessToken.asObservable()).pipe(distinctUntilChanged());
761
799
  }
800
+ forceRefreshSession(customParams, configId) {
801
+ if (!this.refreshSession$) {
802
+ this.refreshSession$ = from(this.runSynchronizedRefresh(customParams, configId)).pipe(finalize(() => {
803
+ this.refreshSession$ = undefined;
804
+ }), shareReplay({ bufferSize: 1, refCount: false }));
805
+ }
806
+ return this.refreshSession$;
807
+ }
808
+ getCsrfToken() {
809
+ return of(null);
810
+ }
762
811
  /**
763
812
  * Indicates whether the current user has the 'admin' role.
764
813
  *
@@ -807,125 +856,480 @@ class KeycloakSecurityService extends OidcSecurityService {
807
856
  return payload.exp < Math.floor(Date.now() / 1000);
808
857
  }));
809
858
  }
810
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
811
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService }); }
812
- }
813
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, decorators: [{
814
- type: Injectable
815
- }], ctorParameters: () => [] });
816
-
817
- class BaseModuleComponent {
818
- static { this.readRight = 'read'; }
819
- static { this.editRight = 'edit'; }
820
- static { this.deleteRight = 'delete'; }
821
- /**
822
- * Updates local settings and persists them to local storage.
823
- * @return {void}
824
- */
825
- onConfigUpdate() {
826
- if (Object.keys(this.localSettings()).length > 0) {
827
- this._localSettings = { ...this.localSettings() };
828
- this.localSettingsUpdate.next(this._localSettings);
829
- localStorage.setItem(`Instance_${this.id}`, JSON.stringify(this._localSettings));
859
+ async runSynchronizedRefresh(customParams, configId) {
860
+ const webLocks = this.getWebLocks();
861
+ if (webLocks) {
862
+ return webLocks.request(this.getRefreshLockKey(configId), { mode: 'exclusive' }, async () => {
863
+ const currentState = await this.syncAuthState(configId);
864
+ if (!(await this.isCurrentAccessTokenExpired())) {
865
+ return currentState;
866
+ }
867
+ return this.refreshAsLockOwner(customParams, configId);
868
+ });
830
869
  }
870
+ return this.runSynchronizedRefreshWithStorageLock(customParams, configId);
831
871
  }
832
- getLocalStorageSettings() {
833
- try {
834
- const localStorageSettingsString = localStorage.getItem(`Instance_${this.id}`);
835
- if (localStorageSettingsString != null) {
836
- this.localSettings.set(JSON.parse(localStorageSettingsString));
872
+ async runSynchronizedRefreshWithStorageLock(customParams, configId) {
873
+ const startedAt = Date.now();
874
+ if (this.tryAcquireRefreshLock(configId)) {
875
+ try {
876
+ const currentState = await this.syncAuthState(configId);
877
+ if (!(await this.isCurrentAccessTokenExpired())) {
878
+ return currentState;
879
+ }
880
+ return await this.refreshAsLockOwner(customParams, configId);
837
881
  }
838
- else {
839
- this.localSettings.set({});
882
+ finally {
883
+ this.releaseRefreshLock(configId);
840
884
  }
841
885
  }
886
+ const waitResult = await this.waitForRefreshResult(startedAt, configId);
887
+ if (waitResult === 'success') {
888
+ return this.syncAuthState(configId);
889
+ }
890
+ if (waitResult === 'error') {
891
+ throw new Error('Token refresh failed in another tab.');
892
+ }
893
+ return this.runSynchronizedRefreshWithStorageLock(customParams, configId);
894
+ }
895
+ async refreshAsLockOwner(customParams, configId) {
896
+ try {
897
+ const response = await firstValueFrom(super.forceRefreshSession(customParams, configId));
898
+ await this.applyLoginResponse(response, configId);
899
+ this.publishRefreshResult('success', configId);
900
+ return response;
901
+ }
842
902
  catch (error) {
843
- this.msgService.error(error, 'Error parsing layoutConfig:');
844
- this.localSettings.set({});
903
+ this.publishRefreshResult('error', configId);
904
+ throw error;
845
905
  }
846
906
  }
847
- /**
848
- * Checks if the content ID is present.
849
- * @returns {boolean} True if the content ID is present, false otherwise.
850
- */
851
- get isContent() {
852
- return this.topBarService.checkId('content');
907
+ async syncAuthState(configId) {
908
+ const response = await firstValueFrom(super.checkAuth(undefined, configId));
909
+ await this.applyLoginResponse(response, configId);
910
+ return response;
911
+ }
912
+ async applyLoginResponse(response, configId) {
913
+ this.loginResponse.next(response);
914
+ this.currentUser.next(response.userData);
915
+ const token = await firstValueFrom(super.getAccessToken(configId));
916
+ this.accessToken.next(token);
917
+ const payload = await firstValueFrom(this.getPayloadFromAccessToken());
918
+ this.payload.next(payload);
919
+ }
920
+ async isCurrentAccessTokenExpired() {
921
+ const payload = await firstValueFrom(this.getPayloadFromAccessToken());
922
+ return !payload?.exp || payload.exp < Math.floor(Date.now() / 1000);
923
+ }
924
+ tryAcquireRefreshLock(configId) {
925
+ const lockKey = this.getRefreshLockKey(configId);
926
+ const now = Date.now();
927
+ const currentLock = this.readRefreshLock(lockKey);
928
+ if (currentLock && currentLock.ownerId !== this.refreshTabId && currentLock.expiresAt > now) {
929
+ return false;
930
+ }
931
+ const nextLock = {
932
+ ownerId: this.refreshTabId,
933
+ expiresAt: now + this.refreshLockTtlMs
934
+ };
935
+ localStorage.setItem(lockKey, JSON.stringify(nextLock));
936
+ return this.readRefreshLock(lockKey)?.ownerId === this.refreshTabId;
937
+ }
938
+ releaseRefreshLock(configId) {
939
+ const lockKey = this.getRefreshLockKey(configId);
940
+ const currentLock = this.readRefreshLock(lockKey);
941
+ if (currentLock?.ownerId === this.refreshTabId) {
942
+ localStorage.removeItem(lockKey);
943
+ }
853
944
  }
854
- /**
855
- * Checks if the settings ID is present.
856
- * @returns {boolean} True if the settings ID is present, false otherwise.
857
- */
858
- get isSettings() {
859
- return this.topBarService.checkId('settings');
945
+ waitForRefreshResult(startedAt, configId) {
946
+ const resultKey = this.getRefreshResultKey(configId);
947
+ return new Promise((resolve) => {
948
+ const timeoutId = window.setTimeout(() => {
949
+ cleanup();
950
+ resolve('timeout');
951
+ }, this.refreshWaitTimeoutMs);
952
+ const intervalId = window.setInterval(() => {
953
+ const result = this.tryResolveRefreshResult(resultKey, startedAt, configId);
954
+ if (result) {
955
+ cleanup();
956
+ resolve(result);
957
+ }
958
+ }, 250);
959
+ const onStorage = (event) => {
960
+ if (event.key === resultKey) {
961
+ const result = this.tryResolveRefreshResult(resultKey, startedAt, configId);
962
+ if (result) {
963
+ cleanup();
964
+ resolve(result);
965
+ }
966
+ }
967
+ };
968
+ const cleanup = () => {
969
+ window.clearTimeout(timeoutId);
970
+ window.clearInterval(intervalId);
971
+ window.removeEventListener('storage', onStorage);
972
+ };
973
+ window.addEventListener('storage', onStorage);
974
+ const result = this.tryResolveRefreshResult(resultKey, startedAt, configId);
975
+ if (result) {
976
+ cleanup();
977
+ resolve(result);
978
+ }
979
+ });
860
980
  }
861
- /**
862
- * Checks if the security ID is present.
863
- * @returns {boolean} True if the security ID is present, false otherwise.
864
- */
865
- get isSecurity() {
866
- return this.topBarService.checkId('security');
981
+ tryResolveRefreshResult(resultKey, startedAt, configId) {
982
+ const result = this.readRefreshResult(resultKey);
983
+ if (!result || result.timestamp < startedAt || result.configId !== configId) {
984
+ return null;
985
+ }
986
+ return result.status;
987
+ }
988
+ publishRefreshResult(status, configId) {
989
+ const result = {
990
+ configId,
991
+ ownerId: this.refreshTabId,
992
+ status,
993
+ timestamp: Date.now()
994
+ };
995
+ localStorage.setItem(this.getRefreshResultKey(configId), JSON.stringify(result));
867
996
  }
868
- /**
869
- * Gets an instant translation for a key or an array of keys.
870
- */
871
- t(key, interpolateParams) {
872
- return this.translateService.instant(key, interpolateParams);
997
+ readRefreshLock(lockKey) {
998
+ try {
999
+ const value = localStorage.getItem(lockKey);
1000
+ return value ? JSON.parse(value) : null;
1001
+ }
1002
+ catch {
1003
+ return null;
1004
+ }
873
1005
  }
874
- /**
875
- * Initializes the component and subscribes to local settings updates.
876
- */
1006
+ readRefreshResult(resultKey) {
1007
+ try {
1008
+ const value = localStorage.getItem(resultKey);
1009
+ return value ? JSON.parse(value) : null;
1010
+ }
1011
+ catch {
1012
+ return null;
1013
+ }
1014
+ }
1015
+ getRefreshLockKey(configId) {
1016
+ return `${this.refreshLockKeyPrefix}:${configId ?? 'default'}`;
1017
+ }
1018
+ getRefreshResultKey(configId) {
1019
+ return `${this.refreshResultKeyPrefix}:${configId ?? 'default'}`;
1020
+ }
1021
+ getWebLocks() {
1022
+ return navigator.locks ?? null;
1023
+ }
1024
+ createRefreshTabId() {
1025
+ return window.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
1026
+ }
1027
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1028
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService }); }
1029
+ }
1030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, decorators: [{
1031
+ type: Injectable
1032
+ }], ctorParameters: () => [] });
1033
+
1034
+ /* eslint-disable */
1035
+ /* tslint:disable */
1036
+ // @ts-nocheck
1037
+ var ContentType;
1038
+ (function (ContentType) {
1039
+ ContentType["Json"] = "application/json";
1040
+ ContentType["JsonApi"] = "application/vnd.api+json";
1041
+ ContentType["FormData"] = "multipart/form-data";
1042
+ ContentType["UrlEncoded"] = "application/x-www-form-urlencoded";
1043
+ ContentType["Text"] = "text/plain";
1044
+ })(ContentType || (ContentType = {}));
1045
+ class HttpClient {
877
1046
  constructor() {
878
- this.isInitialized = false;
879
- this.moduleInstanceReloadPromise = Promise.resolve();
880
- this.destroyRef = inject(DestroyRef);
881
- this.securityDataService = inject(SecurityDataService);
882
1047
  this.securityService = inject(SecurityService);
883
- /**
884
- * Provide access to app settings
885
- */
886
1048
  this.layoutService = inject(LayoutService);
887
- /**
888
- * Provides access to topbar related functionality, such as managing visibility and content.
889
- * @type {TopBarService}
890
- */
891
- this.topBarService = inject(TopBarService);
892
- /**
893
- * Provides access to information about the current route.
894
- * This includes route parameters, data, and the route's path.
895
- */
896
- this.route = inject(ActivatedRoute);
897
- /**
898
- * Provides access to messaging services.
899
- */
900
- this.msgService = inject(MsgService);
901
- /**
902
- * Provides access to base data service functionality.
903
- * @deprecated The method should not be used
904
- */
905
- this.baseDataService = inject(BaseDataService);
906
- /**
907
- * Provides access to translation functionality.
908
- */
909
- this.translateService = inject(TranslateService);
910
- /**
911
- * Provides access to the application's title service.
912
- */
913
- this.appTitleService = inject(AppTitleService);
914
- /**
915
- * Reference to the ChangeDetector. Used to trigger change detection manually.
916
- */
917
- this.changeDetectorRef = inject(ChangeDetectorRef);
918
- /**
919
- * Represents a subscription to an observable.
920
- * Manages the lifecycle of receiving data from the observable and allows unsubscribing to stop receiving data.
921
- * @type {Subscription}
922
- */
923
- this.subscriptions = [];
924
- /**
925
- * Configuration object for backend storage
926
- * @type {TBackendStoreSettings}
927
- */
928
- this.settings = {};
1049
+ this.baseUrl = "";
1050
+ this.securityWorker = () => ({
1051
+ headers: {
1052
+ "Accept-language": this.layoutService.language()
1053
+ ? this.layoutService.language()
1054
+ : "en",
1055
+ "X-Timezone": this.layoutService.timeZone(),
1056
+ },
1057
+ });
1058
+ this.abortControllers = new Map();
1059
+ this.customFetch = (...fetchParams) => fetch(...fetchParams);
1060
+ this.baseApiParams = {
1061
+ credentials: "same-origin",
1062
+ headers: {},
1063
+ redirect: "follow",
1064
+ referrerPolicy: "no-referrer",
1065
+ };
1066
+ this.setSecurityData = (data) => { };
1067
+ this.contentFormatters = {
1068
+ [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string")
1069
+ ? JSON.stringify(input)
1070
+ : input,
1071
+ [ContentType.JsonApi]: (input) => input !== null && (typeof input === "object" || typeof input === "string")
1072
+ ? JSON.stringify(input)
1073
+ : input,
1074
+ [ContentType.Text]: (input) => input !== null && typeof input !== "string"
1075
+ ? JSON.stringify(input)
1076
+ : input,
1077
+ [ContentType.FormData]: (input) => {
1078
+ if (input instanceof FormData) {
1079
+ return input;
1080
+ }
1081
+ return Object.keys(input || {}).reduce((formData, key) => {
1082
+ const property = input[key];
1083
+ formData.append(key, property instanceof Blob
1084
+ ? property
1085
+ : typeof property === "object" && property !== null
1086
+ ? JSON.stringify(property)
1087
+ : `${property}`);
1088
+ return formData;
1089
+ }, new FormData());
1090
+ },
1091
+ [ContentType.UrlEncoded]: (input) => this.toQueryString(input),
1092
+ };
1093
+ this.createAbortSignal = (cancelToken) => {
1094
+ if (this.abortControllers.has(cancelToken)) {
1095
+ const abortController = this.abortControllers.get(cancelToken);
1096
+ if (abortController) {
1097
+ return abortController.signal;
1098
+ }
1099
+ return void 0;
1100
+ }
1101
+ const abortController = new AbortController();
1102
+ this.abortControllers.set(cancelToken, abortController);
1103
+ return abortController.signal;
1104
+ };
1105
+ this.abortRequest = (cancelToken) => {
1106
+ const abortController = this.abortControllers.get(cancelToken);
1107
+ if (abortController) {
1108
+ abortController.abort();
1109
+ this.abortControllers.delete(cancelToken);
1110
+ }
1111
+ };
1112
+ this.request = async ({ body, secure, path, type, query, format, baseUrl, cancelToken, ...params }) => {
1113
+ const secureParams = ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
1114
+ this.securityWorker &&
1115
+ (await this.securityWorker(null))) ||
1116
+ {};
1117
+ const csrfParams = await this.getCsrfRequestParams(params.method, path);
1118
+ const requestParams = this.mergeRequestParams(this.mergeRequestParams(params, secureParams), csrfParams);
1119
+ const queryString = query && this.toQueryString(query);
1120
+ const payloadFormatter = this.contentFormatters[type || ContentType.Json];
1121
+ let responseFormat = format || requestParams.format;
1122
+ return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
1123
+ ...requestParams,
1124
+ headers: {
1125
+ ...(requestParams.headers || {}),
1126
+ ...(type && type !== ContentType.FormData
1127
+ ? { "Content-Type": type }
1128
+ : {}),
1129
+ },
1130
+ signal: (cancelToken
1131
+ ? this.createAbortSignal(cancelToken)
1132
+ : requestParams.signal) || null,
1133
+ body: typeof body === "undefined" || body === null
1134
+ ? null
1135
+ : payloadFormatter(body),
1136
+ }).then(async (response) => {
1137
+ const r = response;
1138
+ r.data = null;
1139
+ r.error = null;
1140
+ if (typeof E !== undefined && responseFormat === undefined)
1141
+ responseFormat = "json";
1142
+ const responseToParse = responseFormat ? response.clone() : response;
1143
+ const data = !responseFormat
1144
+ ? r
1145
+ : await responseToParse[responseFormat]()
1146
+ .then((data) => {
1147
+ if (r.ok) {
1148
+ r.data = data;
1149
+ }
1150
+ else {
1151
+ r.error = data;
1152
+ }
1153
+ return r;
1154
+ })
1155
+ .catch((e) => {
1156
+ r.error = e;
1157
+ return r;
1158
+ });
1159
+ if (cancelToken) {
1160
+ this.abortControllers.delete(cancelToken);
1161
+ }
1162
+ if (!response.ok)
1163
+ throw data;
1164
+ return data.data;
1165
+ });
1166
+ };
1167
+ }
1168
+ encodeQueryParam(key, value) {
1169
+ const encodedKey = encodeURIComponent(key);
1170
+ return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
1171
+ }
1172
+ addQueryParam(query, key) {
1173
+ return this.encodeQueryParam(key, query[key]);
1174
+ }
1175
+ addArrayQueryParam(query, key) {
1176
+ const value = query[key];
1177
+ return value.map((v) => this.encodeQueryParam(key, v)).join("&");
1178
+ }
1179
+ toQueryString(rawQuery) {
1180
+ const query = rawQuery || {};
1181
+ const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
1182
+ return keys
1183
+ .map((key) => Array.isArray(query[key])
1184
+ ? this.addArrayQueryParam(query, key)
1185
+ : this.addQueryParam(query, key))
1186
+ .join("&");
1187
+ }
1188
+ addQueryParams(rawQuery) {
1189
+ const queryString = this.toQueryString(rawQuery);
1190
+ return queryString ? `?${queryString}` : "";
1191
+ }
1192
+ mergeRequestParams(params1, params2) {
1193
+ return {
1194
+ ...this.baseApiParams,
1195
+ ...params1,
1196
+ ...(params2 || {}),
1197
+ headers: {
1198
+ ...(this.baseApiParams.headers || {}),
1199
+ ...(params1.headers || {}),
1200
+ ...((params2 && params2.headers) || {}),
1201
+ },
1202
+ };
1203
+ }
1204
+ async getCsrfRequestParams(method, path) {
1205
+ if (!method ||
1206
+ !["POST", "PUT", "PATCH", "DELETE"].includes(method.toUpperCase())) {
1207
+ return {};
1208
+ }
1209
+ if (path.includes("/api/security/create-auth-session") ||
1210
+ path.includes("/api/security/get-auth-csrf-token")) {
1211
+ return {};
1212
+ }
1213
+ const csrfToken = await firstValueFrom(this.securityService.getCsrfToken());
1214
+ return csrfToken?.token
1215
+ ? { headers: { [csrfToken.headerName]: csrfToken.token } }
1216
+ : {};
1217
+ }
1218
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1219
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, providedIn: "root" }); }
1220
+ }
1221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, decorators: [{
1222
+ type: Injectable,
1223
+ args: [{ providedIn: "root" }]
1224
+ }] });
1225
+
1226
+ class BaseModuleComponent {
1227
+ static { this.readRight = 'read'; }
1228
+ static { this.editRight = 'edit'; }
1229
+ static { this.deleteRight = 'delete'; }
1230
+ /**
1231
+ * Updates local settings and persists them to local storage.
1232
+ * @return {void}
1233
+ */
1234
+ onConfigUpdate() {
1235
+ if (Object.keys(this.localSettings()).length > 0) {
1236
+ this._localSettings = { ...this.localSettings() };
1237
+ this.localSettingsUpdate.next(this._localSettings);
1238
+ localStorage.setItem(`Instance_${this.id}`, JSON.stringify(this._localSettings));
1239
+ }
1240
+ }
1241
+ getLocalStorageSettings() {
1242
+ try {
1243
+ const localStorageSettingsString = localStorage.getItem(`Instance_${this.id}`);
1244
+ if (localStorageSettingsString != null) {
1245
+ this.localSettings.set(JSON.parse(localStorageSettingsString));
1246
+ }
1247
+ else {
1248
+ this.localSettings.set({});
1249
+ }
1250
+ }
1251
+ catch (error) {
1252
+ this.msgService.error(error, 'Error parsing layoutConfig:');
1253
+ this.localSettings.set({});
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Checks if the content ID is present.
1258
+ * @returns {boolean} True if the content ID is present, false otherwise.
1259
+ */
1260
+ get isContent() {
1261
+ return this.topBarService.checkId('content');
1262
+ }
1263
+ /**
1264
+ * Checks if the settings ID is present.
1265
+ * @returns {boolean} True if the settings ID is present, false otherwise.
1266
+ */
1267
+ get isSettings() {
1268
+ return this.topBarService.checkId('settings');
1269
+ }
1270
+ /**
1271
+ * Checks if the security ID is present.
1272
+ * @returns {boolean} True if the security ID is present, false otherwise.
1273
+ */
1274
+ get isSecurity() {
1275
+ return this.topBarService.checkId('security');
1276
+ }
1277
+ /**
1278
+ * Gets an instant translation for a key or an array of keys.
1279
+ */
1280
+ t(key, interpolateParams) {
1281
+ return this.translateService.instant(key, interpolateParams);
1282
+ }
1283
+ /**
1284
+ * Initializes the component and subscribes to local settings updates.
1285
+ */
1286
+ constructor() {
1287
+ this.isInitialized = false;
1288
+ this.moduleInstanceReloadPromise = Promise.resolve();
1289
+ this.destroyRef = inject(DestroyRef);
1290
+ this.securityService = inject(SecurityService);
1291
+ this.httpClient = inject(HttpClient);
1292
+ /**
1293
+ * Provide access to app settings
1294
+ */
1295
+ this.layoutService = inject(LayoutService);
1296
+ /**
1297
+ * Provides access to topbar related functionality, such as managing visibility and content.
1298
+ * @type {TopBarService}
1299
+ */
1300
+ this.topBarService = inject(TopBarService);
1301
+ /**
1302
+ * Provides access to information about the current route.
1303
+ * This includes route parameters, data, and the route's path.
1304
+ */
1305
+ this.route = inject(ActivatedRoute);
1306
+ /**
1307
+ * Provides access to messaging services.
1308
+ */
1309
+ this.msgService = inject(MsgService);
1310
+ /**
1311
+ * Provides access to translation functionality.
1312
+ */
1313
+ this.translateService = inject(TranslateService);
1314
+ /**
1315
+ * Provides access to the application's title service.
1316
+ */
1317
+ this.appTitleService = inject(AppTitleService);
1318
+ /**
1319
+ * Reference to the ChangeDetector. Used to trigger change detection manually.
1320
+ */
1321
+ this.changeDetectorRef = inject(ChangeDetectorRef);
1322
+ /**
1323
+ * Represents a subscription to an observable.
1324
+ * Manages the lifecycle of receiving data from the observable and allows unsubscribing to stop receiving data.
1325
+ * @type {Subscription}
1326
+ */
1327
+ this.subscriptions = [];
1328
+ /**
1329
+ * Configuration object for backend storage
1330
+ * @type {TBackendStoreSettings}
1331
+ */
1332
+ this.settings = {};
929
1333
  /**
930
1334
  * Configuration object for local storage.
931
1335
  * @type {TLocalStoreSettings}
@@ -1020,7 +1424,10 @@ class BaseModuleComponent {
1020
1424
  */
1021
1425
  async getSettings() {
1022
1426
  try {
1023
- this.settings = await this.baseDataService.sendRequest(`${this.baseDataService.baseUrl}api/${this.controller}/get-module-instance-settings?id=${this.id}`);
1427
+ if (this.id == null) {
1428
+ return;
1429
+ }
1430
+ this.settings = await this.getModuleInstanceSettings();
1024
1431
  }
1025
1432
  catch (error) {
1026
1433
  this.msgService.error(error);
@@ -1032,8 +1439,11 @@ class BaseModuleComponent {
1032
1439
  * @return {Promise<void>} A promise that resolves when the settings are saved. Reject if an error occurs.
1033
1440
  */
1034
1441
  async saveSettings(settings) {
1035
- await this.baseDataService
1036
- .sendRequest(`api/${this.controller}/put-module-instance-settings`, 'PUT', {
1442
+ if (this.id == null) {
1443
+ this.msgService.error('Module id not passed!');
1444
+ return;
1445
+ }
1446
+ await this.saveModuleInstanceSettings({
1037
1447
  id: this.id,
1038
1448
  settings: settings
1039
1449
  })
@@ -1065,7 +1475,7 @@ class BaseModuleComponent {
1065
1475
  return;
1066
1476
  }
1067
1477
  this.rightsSubscription = this.securityService.payload
1068
- .pipe(switchMap((payload) => from(this.securityDataService.getSecurity(controller, id)).pipe(map((securitySettings) => ({ payload, securitySettings })))), takeUntilDestroyed(this.destroyRef))
1478
+ .pipe(switchMap((payload) => from(this.getSecurity(controller, id)).pipe(map((securitySettings) => ({ payload, securitySettings })))), takeUntilDestroyed(this.destroyRef))
1069
1479
  .subscribe({
1070
1480
  next: ({ payload, securitySettings }) => {
1071
1481
  const roles = payload?.realm_access?.roles ?? [];
@@ -1096,6 +1506,62 @@ class BaseModuleComponent {
1096
1506
  .find((security) => security.code === code)
1097
1507
  ?.roles?.some((role) => roles.includes(role)) ?? false;
1098
1508
  }
1509
+ getSecurity(controller = this.controller, id = this.id) {
1510
+ if (!controller || id == null) {
1511
+ return Promise.resolve([]);
1512
+ }
1513
+ return this.httpClient.request({
1514
+ path: `/api/${controller}/get-security`,
1515
+ method: 'GET',
1516
+ query: { id },
1517
+ secure: true,
1518
+ format: 'json'
1519
+ });
1520
+ }
1521
+ saveSecurity(request, controller = this.controller) {
1522
+ return this.httpClient.request({
1523
+ path: `/api/${controller}/put-security`,
1524
+ method: 'PUT',
1525
+ body: request,
1526
+ secure: true,
1527
+ type: ContentType.Json
1528
+ });
1529
+ }
1530
+ getModuleInstanceSettings(controller = this.controller, id = this.id) {
1531
+ return this.httpClient.request({
1532
+ path: `/api/${controller}/get-module-instance-settings`,
1533
+ method: 'GET',
1534
+ query: { id },
1535
+ secure: true,
1536
+ format: 'json'
1537
+ });
1538
+ }
1539
+ saveModuleInstanceSettings(request, controller = this.controller) {
1540
+ return this.httpClient.request({
1541
+ path: `/api/${controller}/put-module-instance-settings`,
1542
+ method: 'PUT',
1543
+ body: request,
1544
+ secure: true,
1545
+ type: ContentType.Json
1546
+ });
1547
+ }
1548
+ getMigrations(controller = this.controller) {
1549
+ return this.httpClient.request({
1550
+ path: `/api/${controller}/get-migrations`,
1551
+ method: 'GET',
1552
+ secure: true,
1553
+ format: 'json'
1554
+ });
1555
+ }
1556
+ applyModuleMigration(request, controller = this.controller) {
1557
+ return this.httpClient.request({
1558
+ path: `/api/${controller}/apply-migration`,
1559
+ method: 'POST',
1560
+ body: request,
1561
+ secure: true,
1562
+ type: ContentType.Json
1563
+ });
1564
+ }
1099
1565
  async reloadModuleInstance() {
1100
1566
  this.moduleInstanceReloadPromise = this.moduleInstanceReloadPromise.then(async () => {
1101
1567
  await this.getSettings();
@@ -1112,11 +1578,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1112
1578
  args: [{ standalone: true, template: '' }]
1113
1579
  }], ctorParameters: () => [] });
1114
1580
 
1581
+ /* eslint-disable */
1582
+ /* tslint:disable */
1583
+ // @ts-nocheck
1584
+ class SecurityApi extends HttpClient {
1585
+ constructor() {
1586
+ super(...arguments);
1587
+ this.getCurrentAuthSession = (params = {}) => this.request({
1588
+ path: `/api/security/get-current-auth-session`,
1589
+ method: "GET",
1590
+ secure: true,
1591
+ format: "json",
1592
+ ...params,
1593
+ });
1594
+ this.createAuthSession = (params = {}) => this.request({
1595
+ path: `/api/security/create-auth-session`,
1596
+ method: "POST",
1597
+ secure: true,
1598
+ ...params,
1599
+ });
1600
+ this.deleteAuthSession = (params = {}) => this.request({
1601
+ path: `/api/security/delete-auth-session`,
1602
+ method: "POST",
1603
+ secure: true,
1604
+ ...params,
1605
+ });
1606
+ this.getAuthCsrfToken = (params = {}) => this.request({
1607
+ path: `/api/security/get-auth-csrf-token`,
1608
+ method: "GET",
1609
+ secure: true,
1610
+ format: "json",
1611
+ ...params,
1612
+ });
1613
+ this.getKeycloakClientSettings = (params = {}) => this.request({
1614
+ path: `/api/security/get-keycloak-client-settings`,
1615
+ method: "GET",
1616
+ secure: true,
1617
+ format: "json",
1618
+ ...params,
1619
+ });
1620
+ this.getRealmRoles = (params = {}) => this.request({
1621
+ path: `/api/security/get-realm-roles`,
1622
+ method: "GET",
1623
+ secure: true,
1624
+ format: "json",
1625
+ ...params,
1626
+ });
1627
+ }
1628
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1629
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityApi }); }
1630
+ }
1631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityApi, decorators: [{
1632
+ type: Injectable
1633
+ }] });
1634
+
1115
1635
  class SecurityComponent {
1116
1636
  constructor() {
1117
1637
  this.msgService = inject(MsgService);
1118
- this.dataService = inject(SecurityDataService);
1119
1638
  this.translateService = inject(TranslateService);
1639
+ this.httpClient = inject(HttpClient);
1640
+ this.securityApi = inject(SecurityApi);
1120
1641
  this.securityLoadToken = 0;
1121
1642
  this.securityData = [];
1122
1643
  this.roles = [];
@@ -1130,7 +1651,7 @@ class SecurityComponent {
1130
1651
  }
1131
1652
  }
1132
1653
  ngOnInit() {
1133
- this.dataService.getRealmRoles().then((result) => {
1654
+ this.securityApi.getRealmRoles().then((result) => {
1134
1655
  this.roles = result;
1135
1656
  }, (error) => this.msgService.error(error));
1136
1657
  }
@@ -1147,7 +1668,7 @@ class SecurityComponent {
1147
1668
  id: this.id,
1148
1669
  securities: this.securityData
1149
1670
  };
1150
- this.dataService.saveSecurity(this.controller, request).then((result) => {
1671
+ this.saveSecurity(this.controller, request).then((result) => {
1151
1672
  this.msgService.success(this.translateService.instant('securityComponent.savedSecurity'));
1152
1673
  }, (error) => this.msgService.error(error));
1153
1674
  }
@@ -1165,7 +1686,7 @@ class SecurityComponent {
1165
1686
  return;
1166
1687
  }
1167
1688
  try {
1168
- const result = await this.dataService.getSecurity(controller, id);
1689
+ const result = await this.getSecurity(controller, id);
1169
1690
  if (loadToken === this.securityLoadToken) {
1170
1691
  this.securityData = result;
1171
1692
  }
@@ -1176,6 +1697,24 @@ class SecurityComponent {
1176
1697
  }
1177
1698
  }
1178
1699
  }
1700
+ getSecurity(controller, id) {
1701
+ return this.httpClient.request({
1702
+ path: `/api/${controller}/get-security`,
1703
+ method: 'GET',
1704
+ query: { id },
1705
+ secure: true,
1706
+ format: 'json'
1707
+ });
1708
+ }
1709
+ saveSecurity(controller, request) {
1710
+ return this.httpClient.request({
1711
+ path: `/api/${controller}/put-security`,
1712
+ method: 'PUT',
1713
+ body: request,
1714
+ secure: true,
1715
+ type: ContentType.Json
1716
+ });
1717
+ }
1179
1718
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1180
1719
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: SecurityComponent, isStandalone: true, selector: "security", inputs: { id: "id", controller: "controller" }, usesOnChanges: true, ngImport: i0, template: `
1181
1720
  <div class="flex flex-col md:flex-row gap-8">
@@ -1847,6 +2386,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1847
2386
  }]
1848
2387
  }] });
1849
2388
 
2389
+ /* eslint-disable */
2390
+ /* tslint:disable */
2391
+ // @ts-nocheck
2392
+ class UserProfileApi extends HttpClient {
2393
+ constructor() {
2394
+ super(...arguments);
2395
+ this.getUserPhoto = (query, params = {}) => this.request({
2396
+ path: `/api/user-profile/get-user-photo`,
2397
+ method: "GET",
2398
+ query: query,
2399
+ secure: true,
2400
+ format: "blob",
2401
+ ...params,
2402
+ });
2403
+ this.postUserPhoto = (data, params = {}) => this.request({
2404
+ path: `/api/user-profile/post-user-photo`,
2405
+ method: "POST",
2406
+ body: data,
2407
+ secure: true,
2408
+ type: ContentType.FormData,
2409
+ ...params,
2410
+ });
2411
+ this.getSettings = (params = {}) => this.request({
2412
+ path: `/api/user-profile/get-settings`,
2413
+ method: "GET",
2414
+ secure: true,
2415
+ format: "json",
2416
+ ...params,
2417
+ });
2418
+ this.setSettings = (data, params = {}) => this.request({
2419
+ path: `/api/user-profile/set-settings`,
2420
+ method: "PUT",
2421
+ body: data,
2422
+ secure: true,
2423
+ type: ContentType.Json,
2424
+ ...params,
2425
+ });
2426
+ }
2427
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
2428
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileApi }); }
2429
+ }
2430
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileApi, decorators: [{
2431
+ type: Injectable
2432
+ }] });
2433
+
1850
2434
  /**
1851
2435
  * UserService is responsible for retrieving and handling user-related data,
1852
2436
  * including the user's photo and short label for avatar display.
@@ -1854,7 +2438,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1854
2438
  class UserService {
1855
2439
  constructor() {
1856
2440
  this.securityService = inject(SecurityService);
1857
- this.baseDataService = inject(BaseDataService);
2441
+ this.userProfileApi = inject(UserProfileApi);
1858
2442
  this.requestedPhotoEmail = null;
1859
2443
  /**
1860
2444
  * Stores the user's photo as a data URL or binary blob, depending on how it's processed.
@@ -1879,11 +2463,18 @@ class UserService {
1879
2463
  const data = this.securityService.getCurrentUser();
1880
2464
  const givenNameInitial = data?.given_name?.trim()?.[0];
1881
2465
  const familyNameInitial = data?.family_name?.trim()?.[0];
1882
- return `${givenNameInitial ?? ''}${familyNameInitial ?? ''}`.toUpperCase();
2466
+ const displayNameInitials = this.getInitials(data?.displayName ?? data?.name ?? data?.userName ?? data?.preferred_username);
2467
+ return `${givenNameInitial ?? ''}${familyNameInitial ?? ''}`.toUpperCase() || displayNameInitials;
1883
2468
  }
1884
2469
  get userName() {
1885
2470
  const data = this.securityService.getCurrentUser();
1886
- return [data?.given_name, data?.family_name].filter(Boolean).join(' ');
2471
+ return [data?.given_name, data?.family_name].filter(Boolean).join(' ')
2472
+ || data?.displayName
2473
+ || data?.name
2474
+ || data?.userName
2475
+ || data?.preferred_username
2476
+ || data?.email
2477
+ || '';
1887
2478
  }
1888
2479
  /**
1889
2480
  * Initiates an HTTP request to fetch the user's photo based on their email,
@@ -1898,8 +2489,7 @@ class UserService {
1898
2489
  return;
1899
2490
  }
1900
2491
  this.requestedPhotoEmail = email;
1901
- const url = this.baseDataService.buildUrl(`api/user-profile/get-user-photo?email=${email}`);
1902
- this.baseDataService.getBlob(url).then((data) => {
2492
+ this.userProfileApi.getUserPhoto({ email }, { format: 'blob' }).then((data) => {
1903
2493
  this.createImageFromBlob(data);
1904
2494
  this.photoLoaded = true;
1905
2495
  }, (error) => {
@@ -1921,6 +2511,16 @@ class UserService {
1921
2511
  reader.readAsDataURL(image);
1922
2512
  }
1923
2513
  }
2514
+ getInitials(value) {
2515
+ return value
2516
+ ?.trim()
2517
+ .split(/\s+/)
2518
+ .filter(Boolean)
2519
+ .slice(0, 2)
2520
+ .map((part) => part[0])
2521
+ .join('')
2522
+ .toUpperCase() ?? '';
2523
+ }
1924
2524
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1925
2525
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserService }); }
1926
2526
  }
@@ -2009,141 +2609,417 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2009
2609
  args: [{ providedIn: 'root' }]
2010
2610
  }] });
2011
2611
 
2012
- class AppTopbar {
2612
+ /* eslint-disable */
2613
+ /* tslint:disable */
2614
+ // @ts-nocheck
2615
+ class NotificationApi extends HttpClient {
2616
+ constructor() {
2617
+ super(...arguments);
2618
+ this.getNotificationByUser = (query, params = {}) => this.request({
2619
+ path: `/api/notification/get-notification-by-user`,
2620
+ method: "GET",
2621
+ query: query,
2622
+ secure: true,
2623
+ format: "json",
2624
+ ...params,
2625
+ });
2626
+ this.getNotificationCountByUser = (params = {}) => this.request({
2627
+ path: `/api/notification/get-notification-count-by-user`,
2628
+ method: "GET",
2629
+ secure: true,
2630
+ format: "json",
2631
+ ...params,
2632
+ });
2633
+ this.markNotificationAsRead = ({ id, ...query }, params = {}) => this.request({
2634
+ path: `/api/notification/mark-notification-as-read/${id}`,
2635
+ method: "POST",
2636
+ secure: true,
2637
+ ...params,
2638
+ });
2639
+ this.getNotificationById = (query, params = {}) => this.request({
2640
+ path: `/api/notification/get-notification-by-id`,
2641
+ method: "GET",
2642
+ query: query,
2643
+ secure: true,
2644
+ format: "json",
2645
+ ...params,
2646
+ });
2647
+ }
2648
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
2649
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationApi }); }
2650
+ }
2651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationApi, decorators: [{
2652
+ type: Injectable
2653
+ }] });
2654
+
2655
+ class NotificationService {
2013
2656
  constructor() {
2014
2657
  this.securityService = inject(SecurityService);
2015
- this.topBarService = inject(TopBarService);
2016
- this.userService = inject(UserService);
2017
- this.layoutService = inject(LayoutService);
2018
- this.logoService = inject(LogoService);
2658
+ this.msgService = inject(MsgService);
2659
+ this.notificationApi = inject(NotificationApi);
2660
+ this.unreadNotificationCount = signal(undefined, ...(ngDevMode ? [{ debugName: "unreadNotificationCount" }] : []));
2661
+ this.connection = new signalR.HubConnectionBuilder()
2662
+ .withUrl('/hubs/notification', {
2663
+ withCredentials: true,
2664
+ skipNegotiation: true,
2665
+ transport: signalR.HttpTransportType.WebSockets
2666
+ })
2667
+ .configureLogging(signalR.LogLevel.Error)
2668
+ .build();
2669
+ this.connection.on('ReceiveNotification', (notification) => {
2670
+ const opt = {
2671
+ severity: notification.severity,
2672
+ summary: notification.subject,
2673
+ detail: notification.message,
2674
+ life: 0
2675
+ };
2676
+ this.msgService.add(opt);
2677
+ this.unreadNotificationCount.update((count) => (count ?? 0) + 1);
2678
+ });
2679
+ this.securityService.isAuthenticated().subscribe((authenticated) => {
2680
+ if (!authenticated) {
2681
+ this.unreadNotificationCount.set(undefined);
2682
+ if (this.connection.state !== signalR.HubConnectionState.Disconnected) {
2683
+ this.connection.stop();
2684
+ }
2685
+ return;
2686
+ }
2687
+ this.loadUnreadNotificationCount();
2688
+ if (this.connection.state === signalR.HubConnectionState.Disconnected) {
2689
+ this.connection.start().catch((error) => console.error('Failed to start notification connection', error));
2690
+ return;
2691
+ }
2692
+ this.connection.stop().then(() => {
2693
+ this.connection.start().catch((error) => console.error('Failed to restart notification connection', error));
2694
+ });
2695
+ });
2019
2696
  }
2020
- toggleDarkMode() {
2021
- this.layoutService.layoutConfig.update((state) => ({
2022
- ...state,
2023
- darkTheme: !state.darkTheme
2024
- }));
2697
+ loadUnreadNotificationCount() {
2698
+ this.notificationApi
2699
+ .getNotificationCountByUser()
2700
+ .then((response) => this.unreadNotificationCount.set(response.count ?? 0))
2701
+ .catch((error) => console.error('Failed to load notification count', error));
2025
2702
  }
2026
- logoutKeyDown($event) {
2027
- if ($event.key === 'Enter') {
2028
- this.securityService.logout();
2703
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2704
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, providedIn: 'root' }); }
2705
+ }
2706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, decorators: [{
2707
+ type: Injectable,
2708
+ args: [{ providedIn: 'root' }]
2709
+ }], ctorParameters: () => [] });
2710
+
2711
+ class UserNotificationsComponent {
2712
+ constructor() {
2713
+ this.notifications = [];
2714
+ this.totalCount = 0;
2715
+ this.skip = 0;
2716
+ this.take = 5;
2717
+ this.loading = false;
2718
+ this.notificationApi = inject(NotificationApi);
2719
+ this.notificationService = inject(NotificationService);
2720
+ this.msgService = inject(MsgService);
2721
+ this.translateService = inject(TranslateService);
2722
+ this.unreadNotificationCount = computed(() => this.notificationService.unreadNotificationCount() ?? 0, ...(ngDevMode ? [{ debugName: "unreadNotificationCount" }] : []));
2723
+ this.unreadNotificationBadge = computed(() => {
2724
+ const count = this.unreadNotificationCount();
2725
+ return count > 99 ? '99+' : count.toString();
2726
+ }, ...(ngDevMode ? [{ debugName: "unreadNotificationBadge" }] : []));
2727
+ }
2728
+ async toggle(event) {
2729
+ this.popover?.toggle(event);
2730
+ await this.loadNotifications();
2731
+ }
2732
+ async loadNotifications() {
2733
+ this.loading = true;
2734
+ try {
2735
+ const response = await this.notificationApi.getNotificationByUser({
2736
+ skip: this.skip,
2737
+ take: this.take,
2738
+ unreadOnly: true
2739
+ });
2740
+ this.notifications = response.notifications ?? [];
2741
+ this.totalCount = response.totalCount ?? 0;
2742
+ }
2743
+ catch (error) {
2744
+ this.msgService.error(error);
2745
+ }
2746
+ finally {
2747
+ this.loading = false;
2029
2748
  }
2030
2749
  }
2031
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2032
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AppTopbar, isStandalone: true, selector: "app-topbar", ngImport: i0, template: ` <div class="layout-topbar">
2033
- <div class="layout-topbar-logo-container">
2034
- <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
2035
- <i class="pi pi-bars"></i>
2036
- </button>
2037
- <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
2038
- <ng-container
2039
- *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
2040
- <span>{{ 'app-info.title' | translate }}</span>
2041
- </a>
2042
- </div>
2043
-
2044
- @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
2045
- <p-tabs class="layout-topbar-tabs ml-2" [(value)]="topBarService.activeId">
2046
- <p-tablist>
2047
- @for (tab of topBarService.availableTopBarItems; track tab.id) {
2048
- <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
2049
- <i class="pi {{ tab.icon }}"></i>
2050
- <span class="ml-2">{{ tab.caption }}</span>
2051
- </p-tab>
2052
- }
2053
- </p-tablist>
2054
- </p-tabs>
2750
+ async onPageChange(event) {
2751
+ this.skip = event.first ?? 0;
2752
+ this.take = event.rows ?? this.take;
2753
+ await this.loadNotifications();
2055
2754
  }
2056
- <div class="layout-topbar-actions">
2057
- <div class="layout-config-menu">
2755
+ async markAsRead(notification) {
2756
+ if (!notification.notificationUserId) {
2757
+ return;
2758
+ }
2759
+ this.readingNotificationId = notification.notificationUserId;
2760
+ try {
2761
+ await this.notificationApi.markNotificationAsRead({ id: notification.notificationUserId });
2762
+ this.msgService.success(this.translateService.instant('userNotifications.markedAsRead'));
2763
+ this.notificationService.loadUnreadNotificationCount();
2764
+ if (this.notifications.length === 1 && this.skip > 0) {
2765
+ this.skip = Math.max(this.skip - this.take, 0);
2766
+ }
2767
+ await this.loadNotifications();
2768
+ }
2769
+ catch (error) {
2770
+ this.msgService.error(error);
2771
+ }
2772
+ finally {
2773
+ this.readingNotificationId = undefined;
2774
+ }
2775
+ }
2776
+ getImportanceSeverity(importance) {
2777
+ switch (importance) {
2778
+ case 'Critical':
2779
+ case 'High':
2780
+ return 'danger';
2781
+ case 'Medium':
2782
+ return 'warn';
2783
+ case 'Low':
2784
+ return 'info';
2785
+ default:
2786
+ return 'secondary';
2787
+ }
2788
+ }
2789
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2790
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: UserNotificationsComponent, isStandalone: true, selector: "app-user-notifications", viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
2791
+ <button
2792
+ class="layout-topbar-action relative"
2793
+ id="oip-app-topbar-notification-button"
2794
+ type="button"
2795
+ (click)="toggle($event)"
2796
+ [attr.aria-label]="'Notifications: ' + unreadNotificationCount()">
2797
+ <i class="pi pi-bell"></i>
2798
+ @if (unreadNotificationCount() > 0) {
2799
+ <p-badge
2800
+ class="absolute -top-1 -right-1"
2801
+ severity="danger"
2802
+ [badgeSize]="'small'"
2803
+ [value]="unreadNotificationBadge()" />
2804
+ }
2805
+ </button>
2806
+
2807
+ <p-popover #popover styleClass="w-[min(92vw,34rem)]">
2808
+ <div class="flex items-center justify-between gap-3 mb-4">
2809
+ <div class="font-semibold text-lg">
2810
+ {{ 'userNotifications.title' | translate }}
2811
+ </div>
2058
2812
  <p-button
2059
- class="layout-topbar-action"
2060
- id="oip-app-topbar-theme-button"
2813
+ icon="pi pi-refresh"
2814
+ rounded="true"
2061
2815
  severity="secondary"
2062
- type="button"
2063
- [rounded]="true"
2064
- [text]="true"
2065
- (click)="toggleDarkMode()">
2066
- <i
2067
- class="pi"
2068
- [ngClass]="{
2069
- 'pi-moon': layoutService.isDarkTheme(),
2070
- 'pi-sun': !layoutService.isDarkTheme()
2071
- }"></i>
2072
- </p-button>
2073
- <div class="relative">
2074
- <p-button
2075
- class="layout-topbar-action layout-topbar-action-highlight"
2076
- enterActiveClass="animate-scalein"
2077
- enterFromClass="hidden"
2078
- id="oip-app-topbar-palette-button"
2079
- leaveActiveClass="animate-fadeout"
2080
- leaveToClass="hidden"
2081
- pStyleClass="@next"
2082
- [hideOnOutsideClick]="true"
2083
- [rounded]="true">
2084
- <i class="pi pi-palette"></i>
2085
- </p-button>
2086
- <app-configurator />
2816
+ text="true"
2817
+ tooltipPosition="bottom"
2818
+ [disabled]="loading"
2819
+ [pTooltip]="'userNotifications.refresh' | translate"
2820
+ (onClick)="loadNotifications()"></p-button>
2821
+ </div>
2822
+
2823
+ @if (loading && notifications.length === 0) {
2824
+ <div class="flex justify-center py-8">
2825
+ <p-progressSpinner style="w-8 h-8"></p-progressSpinner>
2826
+ </div>
2827
+ } @else if (notifications.length === 0) {
2828
+ <div class="text-center py-8 text-surface-500">
2829
+ {{ 'userNotifications.empty' | translate }}
2830
+ </div>
2831
+ } @else {
2832
+ <div class="flex flex-col gap-3 max-h-[26rem] overflow-y-auto pr-1">
2833
+ @for (notification of notifications; track notification.notificationUserId) {
2834
+ <div class="border border-surface-200 dark:border-surface-700 rounded-md p-3">
2835
+ <div class="flex items-start justify-between gap-3">
2836
+ <div class="min-w-0">
2837
+ <div class="font-medium leading-snug break-words">
2838
+ {{ notification.subject }}
2839
+ </div>
2840
+ <div class="text-xs text-surface-500 mt-1">
2841
+ {{ notification.createdAt | date: 'dd.MM.yyyy HH:mm' }}
2842
+ </div>
2843
+ </div>
2844
+ @if (notification.importance) {
2845
+ <p-tag
2846
+ class="shrink-0"
2847
+ [severity]="getImportanceSeverity(notification.importance)"
2848
+ [value]="notification.importance"></p-tag>
2849
+ }
2850
+ </div>
2851
+
2852
+ <div class="text-sm text-surface-700 dark:text-surface-200 mt-3 whitespace-pre-line break-words">
2853
+ {{ notification.message }}
2854
+ </div>
2855
+
2856
+ <div class="flex justify-end mt-3">
2857
+ <p-button
2858
+ icon="pi pi-check"
2859
+ size="small"
2860
+ [label]="'userNotifications.markAsRead' | translate"
2861
+ [loading]="readingNotificationId === notification.notificationUserId"
2862
+ (onClick)="markAsRead(notification)"></p-button>
2863
+ </div>
2864
+ </div>
2865
+ }
2866
+ </div>
2867
+
2868
+ <p-paginator
2869
+ styleClass="mt-3"
2870
+ [first]="skip"
2871
+ [rows]="take"
2872
+ [totalRecords]="totalCount"
2873
+ [rowsPerPageOptions]="[5, 10, 20]"
2874
+ (onPageChange)="onPageChange($event)"></p-paginator>
2875
+ }
2876
+ </p-popover>
2877
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i1$3.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i3$1.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first", "appendTo"], outputs: ["onPageChange"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
2878
+ }
2879
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserNotificationsComponent, decorators: [{
2880
+ type: Component,
2881
+ args: [{
2882
+ selector: 'app-user-notifications',
2883
+ standalone: true,
2884
+ imports: [CommonModule, BadgeModule, ButtonModule, PaginatorModule, Popover, ProgressSpinnerModule, Tag, Tooltip, TranslatePipe],
2885
+ template: `
2886
+ <button
2887
+ class="layout-topbar-action relative"
2888
+ id="oip-app-topbar-notification-button"
2889
+ type="button"
2890
+ (click)="toggle($event)"
2891
+ [attr.aria-label]="'Notifications: ' + unreadNotificationCount()">
2892
+ <i class="pi pi-bell"></i>
2893
+ @if (unreadNotificationCount() > 0) {
2894
+ <p-badge
2895
+ class="absolute -top-1 -right-1"
2896
+ severity="danger"
2897
+ [badgeSize]="'small'"
2898
+ [value]="unreadNotificationBadge()" />
2899
+ }
2900
+ </button>
2901
+
2902
+ <p-popover #popover styleClass="w-[min(92vw,34rem)]">
2903
+ <div class="flex items-center justify-between gap-3 mb-4">
2904
+ <div class="font-semibold text-lg">
2905
+ {{ 'userNotifications.title' | translate }}
2087
2906
  </div>
2907
+ <p-button
2908
+ icon="pi pi-refresh"
2909
+ rounded="true"
2910
+ severity="secondary"
2911
+ text="true"
2912
+ tooltipPosition="bottom"
2913
+ [disabled]="loading"
2914
+ [pTooltip]="'userNotifications.refresh' | translate"
2915
+ (onClick)="loadNotifications()"></p-button>
2088
2916
  </div>
2089
2917
 
2090
- <button
2091
- class="layout-topbar-menu-button layout-topbar-action"
2092
- enterActiveClass="animate-scalein"
2093
- enterFromClass="hidden"
2094
- id="oip-app-topbar-menu-expand-button"
2095
- leaveActiveClass="animate-fadeout"
2096
- leaveToClass="hidden"
2097
- pStyleClass="@next"
2098
- [hideOnOutsideClick]="true">
2099
- <i class="pi pi-ellipsis-v"></i>
2100
- </button>
2918
+ @if (loading && notifications.length === 0) {
2919
+ <div class="flex justify-center py-8">
2920
+ <p-progressSpinner style="w-8 h-8"></p-progressSpinner>
2921
+ </div>
2922
+ } @else if (notifications.length === 0) {
2923
+ <div class="text-center py-8 text-surface-500">
2924
+ {{ 'userNotifications.empty' | translate }}
2925
+ </div>
2926
+ } @else {
2927
+ <div class="flex flex-col gap-3 max-h-[26rem] overflow-y-auto pr-1">
2928
+ @for (notification of notifications; track notification.notificationUserId) {
2929
+ <div class="border border-surface-200 dark:border-surface-700 rounded-md p-3">
2930
+ <div class="flex items-start justify-between gap-3">
2931
+ <div class="min-w-0">
2932
+ <div class="font-medium leading-snug break-words">
2933
+ {{ notification.subject }}
2934
+ </div>
2935
+ <div class="text-xs text-surface-500 mt-1">
2936
+ {{ notification.createdAt | date: 'dd.MM.yyyy HH:mm' }}
2937
+ </div>
2938
+ </div>
2939
+ @if (notification.importance) {
2940
+ <p-tag
2941
+ class="shrink-0"
2942
+ [severity]="getImportanceSeverity(notification.importance)"
2943
+ [value]="notification.importance"></p-tag>
2944
+ }
2945
+ </div>
2101
2946
 
2102
- <div class="layout-topbar-menu hidden lg:block">
2103
- <div class="layout-topbar-menu-content">
2104
- <button
2105
- class="layout-topbar-action"
2106
- id="oip-app-topbar-logout-button"
2107
- type="button"
2108
- (click)="securityService.logout()"
2109
- (keydown)="logoutKeyDown($event)">
2110
- <i class="pi pi-sign-out"></i>
2111
- <span>{{ 'topbar.logout' | translate }}</span>
2112
- </button>
2113
- <button class="layout-topbar-action" routerLink="config">
2114
- <p-avatar
2115
- class="p-link flex align-items-center"
2116
- id="oip-app-topbar-user-avatar"
2117
- shape="circle"
2118
- size="normal"
2119
- [image]="userService.photoLoaded ? userService.photo : null"
2120
- >{{ !userService.photoLoaded ? userService.shortLabel : null }}
2121
- </p-avatar>
2122
- <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
2123
- </button>
2947
+ <div class="text-sm text-surface-700 dark:text-surface-200 mt-3 whitespace-pre-line break-words">
2948
+ {{ notification.message }}
2949
+ </div>
2950
+
2951
+ <div class="flex justify-end mt-3">
2952
+ <p-button
2953
+ icon="pi pi-check"
2954
+ size="small"
2955
+ [label]="'userNotifications.markAsRead' | translate"
2956
+ [loading]="readingNotificationId === notification.notificationUserId"
2957
+ (onClick)="markAsRead(notification)"></p-button>
2958
+ </div>
2959
+ </div>
2960
+ }
2124
2961
  </div>
2125
- </div>
2126
- </div>
2127
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }, { kind: "component", type: Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: TabList, selector: "p-tablist" }, { kind: "component", type: Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
2128
- }
2129
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppTopbar, decorators: [{
2130
- type: Component,
2131
- args: [{
2132
- selector: 'app-topbar',
2133
- standalone: true,
2134
- imports: [
2135
- RouterModule,
2136
- CommonModule,
2137
- StyleClassModule,
2138
- AppConfiguratorComponent,
2139
- Tabs,
2140
- TabList,
2141
- Tab,
2142
- AvatarModule,
2143
- ButtonModule,
2144
- TranslatePipe
2145
- ],
2146
- template: ` <div class="layout-topbar">
2962
+
2963
+ <p-paginator
2964
+ styleClass="mt-3"
2965
+ [first]="skip"
2966
+ [rows]="take"
2967
+ [totalRecords]="totalCount"
2968
+ [rowsPerPageOptions]="[5, 10, 20]"
2969
+ (onPageChange)="onPageChange($event)"></p-paginator>
2970
+ }
2971
+ </p-popover>
2972
+ `
2973
+ }]
2974
+ }], propDecorators: { popover: [{
2975
+ type: ViewChild,
2976
+ args: ['popover']
2977
+ }] } });
2978
+
2979
+ class AppTopbar {
2980
+ constructor() {
2981
+ this.securityService = inject(SecurityService);
2982
+ this.topBarService = inject(TopBarService);
2983
+ this.userService = inject(UserService);
2984
+ this.layoutService = inject(LayoutService);
2985
+ this.logoService = inject(LogoService);
2986
+ this.confirmationService = inject(ConfirmationService);
2987
+ this.translateService = inject(TranslateService);
2988
+ }
2989
+ toggleDarkMode() {
2990
+ this.layoutService.layoutConfig.update((state) => ({
2991
+ ...state,
2992
+ darkTheme: !state.darkTheme
2993
+ }));
2994
+ }
2995
+ logoutKeyDown($event) {
2996
+ if ($event.key === 'Enter') {
2997
+ $event.preventDefault();
2998
+ this.confirmLogout();
2999
+ }
3000
+ }
3001
+ confirmLogout() {
3002
+ this.confirmationService.confirm({
3003
+ header: this.translateService.instant('topbar.logoutConfirmHeader'),
3004
+ message: this.translateService.instant('topbar.logoutConfirmMessage'),
3005
+ icon: 'pi pi-sign-out',
3006
+ rejectButtonProps: {
3007
+ label: this.translateService.instant('topbar.logoutConfirmCancel'),
3008
+ severity: 'secondary',
3009
+ outlined: true
3010
+ },
3011
+ acceptButtonProps: {
3012
+ label: this.translateService.instant('topbar.logoutConfirmAccept'),
3013
+ severity: 'danger'
3014
+ },
3015
+ accept: () => {
3016
+ this.securityService.logout();
3017
+ }
3018
+ });
3019
+ }
3020
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3021
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AppTopbar, isStandalone: true, selector: "app-topbar", providers: [ConfirmationService], ngImport: i0, template: ` <div class="layout-topbar">
3022
+ <p-confirmDialog appendTo="body" />
2147
3023
  <div class="layout-topbar-logo-container">
2148
3024
  <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
2149
3025
  <i class="pi pi-bars"></i>
@@ -2169,6 +3045,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2169
3045
  }
2170
3046
  <div class="layout-topbar-actions">
2171
3047
  <div class="layout-config-menu">
3048
+ <app-user-notifications />
2172
3049
  <p-button
2173
3050
  class="layout-topbar-action"
2174
3051
  id="oip-app-topbar-theme-button"
@@ -2219,7 +3096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2219
3096
  class="layout-topbar-action"
2220
3097
  id="oip-app-topbar-logout-button"
2221
3098
  type="button"
2222
- (click)="securityService.logout()"
3099
+ (click)="confirmLogout()"
2223
3100
  (keydown)="logoutKeyDown($event)">
2224
3101
  <i class="pi pi-sign-out"></i>
2225
3102
  <span>{{ 'topbar.logout' | translate }}</span>
@@ -2238,232 +3115,167 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2238
3115
  </div>
2239
3116
  </div>
2240
3117
  </div>
2241
- </div>`
2242
- }]
2243
- }] });
2244
-
2245
- class FooterComponent {
2246
- constructor() {
2247
- this.logoService = inject(LogoService);
2248
- }
2249
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2250
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: FooterComponent, isStandalone: true, selector: "app-footer", ngImport: i0, template: `
2251
- <div class="layout-footer">
2252
- <div class="flex justify-center flex-1">
2253
- <div class="mr-2 -my-0.5">
2254
- <ng-container
2255
- *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 18, height: 18 }"></ng-container>
2256
- </div>
2257
- <span class="font-medium">{{ 'app-info.footer' | translate }}</span>
2258
- </div>
2259
- <p class="mr-auto">{{ 'app-info.version' | translate }}</p>
2260
- </div>
2261
- `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3118
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$2.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }, { kind: "component", type: Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: TabList, selector: "p-tablist" }, { kind: "component", type: Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: UserNotificationsComponent, selector: "app-user-notifications" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
2262
3119
  }
2263
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FooterComponent, decorators: [{
3120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppTopbar, decorators: [{
2264
3121
  type: Component,
2265
3122
  args: [{
2266
- selector: 'app-footer',
2267
- template: `
2268
- <div class="layout-footer">
2269
- <div class="flex justify-center flex-1">
2270
- <div class="mr-2 -my-0.5">
2271
- <ng-container
2272
- *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 18, height: 18 }"></ng-container>
2273
- </div>
2274
- <span class="font-medium">{{ 'app-info.footer' | translate }}</span>
2275
- </div>
2276
- <p class="mr-auto">{{ 'app-info.version' | translate }}</p>
2277
- </div>
2278
- `,
2279
- standalone: true,
2280
- imports: [TranslatePipe, NgComponentOutlet]
2281
- }]
2282
- }] });
2283
-
2284
- /* eslint-disable */
2285
- /* tslint:disable */
2286
- // @ts-nocheck
2287
- var ContentType;
2288
- (function (ContentType) {
2289
- ContentType["Json"] = "application/json";
2290
- ContentType["JsonApi"] = "application/vnd.api+json";
2291
- ContentType["FormData"] = "multipart/form-data";
2292
- ContentType["UrlEncoded"] = "application/x-www-form-urlencoded";
2293
- ContentType["Text"] = "text/plain";
2294
- })(ContentType || (ContentType = {}));
2295
- class HttpClient {
2296
- constructor() {
2297
- this.securityService = inject(SecurityService);
2298
- this.layoutService = inject(LayoutService);
2299
- this.baseUrl = "";
2300
- this.securityData = null;
2301
- this.securityWorker = (securityData) => ({
2302
- headers: {
2303
- "Accept-language": this.layoutService.language()
2304
- ? this.layoutService.language()
2305
- : "en",
2306
- "X-Timezone": this.layoutService.timeZone(),
2307
- Authorization: `Bearer ${securityData}`,
2308
- },
2309
- });
2310
- this.abortControllers = new Map();
2311
- this.customFetch = (...fetchParams) => fetch(...fetchParams);
2312
- this.baseApiParams = {
2313
- credentials: "same-origin",
2314
- headers: {},
2315
- redirect: "follow",
2316
- referrerPolicy: "no-referrer",
2317
- };
2318
- this.setSecurityData = (data) => {
2319
- this.securityData = data;
2320
- };
2321
- this.contentFormatters = {
2322
- [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string")
2323
- ? JSON.stringify(input)
2324
- : input,
2325
- [ContentType.JsonApi]: (input) => input !== null && (typeof input === "object" || typeof input === "string")
2326
- ? JSON.stringify(input)
2327
- : input,
2328
- [ContentType.Text]: (input) => input !== null && typeof input !== "string"
2329
- ? JSON.stringify(input)
2330
- : input,
2331
- [ContentType.FormData]: (input) => {
2332
- if (input instanceof FormData) {
2333
- return input;
2334
- }
2335
- return Object.keys(input || {}).reduce((formData, key) => {
2336
- const property = input[key];
2337
- formData.append(key, property instanceof Blob
2338
- ? property
2339
- : typeof property === "object" && property !== null
2340
- ? JSON.stringify(property)
2341
- : `${property}`);
2342
- return formData;
2343
- }, new FormData());
2344
- },
2345
- [ContentType.UrlEncoded]: (input) => this.toQueryString(input),
2346
- };
2347
- this.createAbortSignal = (cancelToken) => {
2348
- if (this.abortControllers.has(cancelToken)) {
2349
- const abortController = this.abortControllers.get(cancelToken);
2350
- if (abortController) {
2351
- return abortController.signal;
2352
- }
2353
- return void 0;
2354
- }
2355
- const abortController = new AbortController();
2356
- this.abortControllers.set(cancelToken, abortController);
2357
- return abortController.signal;
2358
- };
2359
- this.abortRequest = (cancelToken) => {
2360
- const abortController = this.abortControllers.get(cancelToken);
2361
- if (abortController) {
2362
- abortController.abort();
2363
- this.abortControllers.delete(cancelToken);
2364
- }
2365
- };
2366
- this.request = async ({ body, secure, path, type, query, format, baseUrl, cancelToken, ...params }) => {
2367
- const secureParams = ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
2368
- this.securityWorker &&
2369
- (await this.securityWorker(this.securityData))) ||
2370
- {};
2371
- const requestParams = this.mergeRequestParams(params, secureParams);
2372
- const queryString = query && this.toQueryString(query);
2373
- const payloadFormatter = this.contentFormatters[type || ContentType.Json];
2374
- let responseFormat = format || requestParams.format;
2375
- return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
2376
- ...requestParams,
2377
- headers: {
2378
- ...(requestParams.headers || {}),
2379
- ...(type && type !== ContentType.FormData
2380
- ? { "Content-Type": type }
2381
- : {}),
2382
- },
2383
- signal: (cancelToken
2384
- ? this.createAbortSignal(cancelToken)
2385
- : requestParams.signal) || null,
2386
- body: typeof body === "undefined" || body === null
2387
- ? null
2388
- : payloadFormatter(body),
2389
- }).then(async (response) => {
2390
- const r = response;
2391
- r.data = null;
2392
- r.error = null;
2393
- if (typeof E !== undefined && responseFormat === undefined)
2394
- responseFormat = "json";
2395
- const responseToParse = responseFormat ? response.clone() : response;
2396
- const data = !responseFormat
2397
- ? r
2398
- : await responseToParse[responseFormat]()
2399
- .then((data) => {
2400
- if (r.ok) {
2401
- r.data = data;
2402
- }
2403
- else {
2404
- r.error = data;
2405
- }
2406
- return r;
2407
- })
2408
- .catch((e) => {
2409
- r.error = e;
2410
- return r;
2411
- });
2412
- if (cancelToken) {
2413
- this.abortControllers.delete(cancelToken);
2414
- }
2415
- if (!response.ok)
2416
- throw data;
2417
- return data.data;
2418
- });
2419
- };
2420
- this.securityService.getAccessToken().subscribe((token) => {
2421
- this.securityData = token;
2422
- });
2423
- }
2424
- encodeQueryParam(key, value) {
2425
- const encodedKey = encodeURIComponent(key);
2426
- return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
2427
- }
2428
- addQueryParam(query, key) {
2429
- return this.encodeQueryParam(key, query[key]);
2430
- }
2431
- addArrayQueryParam(query, key) {
2432
- const value = query[key];
2433
- return value.map((v) => this.encodeQueryParam(key, v)).join("&");
2434
- }
2435
- toQueryString(rawQuery) {
2436
- const query = rawQuery || {};
2437
- const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
2438
- return keys
2439
- .map((key) => Array.isArray(query[key])
2440
- ? this.addArrayQueryParam(query, key)
2441
- : this.addQueryParam(query, key))
2442
- .join("&");
2443
- }
2444
- addQueryParams(rawQuery) {
2445
- const queryString = this.toQueryString(rawQuery);
2446
- return queryString ? `?${queryString}` : "";
3123
+ selector: 'app-topbar',
3124
+ standalone: true,
3125
+ imports: [
3126
+ RouterModule,
3127
+ CommonModule,
3128
+ StyleClassModule,
3129
+ AppConfiguratorComponent,
3130
+ Tabs,
3131
+ TabList,
3132
+ Tab,
3133
+ AvatarModule,
3134
+ ButtonModule,
3135
+ ConfirmDialog,
3136
+ UserNotificationsComponent,
3137
+ TranslatePipe
3138
+ ],
3139
+ template: ` <div class="layout-topbar">
3140
+ <p-confirmDialog appendTo="body" />
3141
+ <div class="layout-topbar-logo-container">
3142
+ <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
3143
+ <i class="pi pi-bars"></i>
3144
+ </button>
3145
+ <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
3146
+ <ng-container
3147
+ *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
3148
+ <span>{{ 'app-info.title' | translate }}</span>
3149
+ </a>
3150
+ </div>
3151
+
3152
+ @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
3153
+ <p-tabs class="layout-topbar-tabs ml-2" [(value)]="topBarService.activeId">
3154
+ <p-tablist>
3155
+ @for (tab of topBarService.availableTopBarItems; track tab.id) {
3156
+ <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
3157
+ <i class="pi {{ tab.icon }}"></i>
3158
+ <span class="ml-2">{{ tab.caption }}</span>
3159
+ </p-tab>
3160
+ }
3161
+ </p-tablist>
3162
+ </p-tabs>
2447
3163
  }
2448
- mergeRequestParams(params1, params2) {
2449
- return {
2450
- ...this.baseApiParams,
2451
- ...params1,
2452
- ...(params2 || {}),
2453
- headers: {
2454
- ...(this.baseApiParams.headers || {}),
2455
- ...(params1.headers || {}),
2456
- ...((params2 && params2.headers) || {}),
2457
- },
2458
- };
3164
+ <div class="layout-topbar-actions">
3165
+ <div class="layout-config-menu">
3166
+ <app-user-notifications />
3167
+ <p-button
3168
+ class="layout-topbar-action"
3169
+ id="oip-app-topbar-theme-button"
3170
+ severity="secondary"
3171
+ type="button"
3172
+ [rounded]="true"
3173
+ [text]="true"
3174
+ (click)="toggleDarkMode()">
3175
+ <i
3176
+ class="pi"
3177
+ [ngClass]="{
3178
+ 'pi-moon': layoutService.isDarkTheme(),
3179
+ 'pi-sun': !layoutService.isDarkTheme()
3180
+ }"></i>
3181
+ </p-button>
3182
+ <div class="relative">
3183
+ <p-button
3184
+ class="layout-topbar-action layout-topbar-action-highlight"
3185
+ enterActiveClass="animate-scalein"
3186
+ enterFromClass="hidden"
3187
+ id="oip-app-topbar-palette-button"
3188
+ leaveActiveClass="animate-fadeout"
3189
+ leaveToClass="hidden"
3190
+ pStyleClass="@next"
3191
+ [hideOnOutsideClick]="true"
3192
+ [rounded]="true">
3193
+ <i class="pi pi-palette"></i>
3194
+ </p-button>
3195
+ <app-configurator />
3196
+ </div>
3197
+ </div>
3198
+
3199
+ <button
3200
+ class="layout-topbar-menu-button layout-topbar-action"
3201
+ enterActiveClass="animate-scalein"
3202
+ enterFromClass="hidden"
3203
+ id="oip-app-topbar-menu-expand-button"
3204
+ leaveActiveClass="animate-fadeout"
3205
+ leaveToClass="hidden"
3206
+ pStyleClass="@next"
3207
+ [hideOnOutsideClick]="true">
3208
+ <i class="pi pi-ellipsis-v"></i>
3209
+ </button>
3210
+
3211
+ <div class="layout-topbar-menu hidden lg:block">
3212
+ <div class="layout-topbar-menu-content">
3213
+ <button
3214
+ class="layout-topbar-action"
3215
+ id="oip-app-topbar-logout-button"
3216
+ type="button"
3217
+ (click)="confirmLogout()"
3218
+ (keydown)="logoutKeyDown($event)">
3219
+ <i class="pi pi-sign-out"></i>
3220
+ <span>{{ 'topbar.logout' | translate }}</span>
3221
+ </button>
3222
+ <button class="layout-topbar-action" routerLink="config">
3223
+ <p-avatar
3224
+ class="p-link flex align-items-center"
3225
+ id="oip-app-topbar-user-avatar"
3226
+ shape="circle"
3227
+ size="normal"
3228
+ [image]="userService.photoLoaded ? userService.photo : null"
3229
+ >{{ !userService.photoLoaded ? userService.shortLabel : null }}
3230
+ </p-avatar>
3231
+ <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
3232
+ </button>
3233
+ </div>
3234
+ </div>
3235
+ </div>
3236
+ </div>`,
3237
+ providers: [ConfirmationService]
3238
+ }]
3239
+ }] });
3240
+
3241
+ class FooterComponent {
3242
+ constructor() {
3243
+ this.logoService = inject(LogoService);
2459
3244
  }
2460
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2461
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, providedIn: "root" }); }
3245
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3246
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: FooterComponent, isStandalone: true, selector: "app-footer", ngImport: i0, template: `
3247
+ <div class="layout-footer">
3248
+ <div class="flex justify-center flex-1">
3249
+ <div class="mr-2 -my-0.5">
3250
+ <ng-container
3251
+ *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 18, height: 18 }"></ng-container>
3252
+ </div>
3253
+ <span class="font-medium">{{ 'app-info.footer' | translate }}</span>
3254
+ </div>
3255
+ <p class="mr-auto">{{ 'app-info.version' | translate }}</p>
3256
+ </div>
3257
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
2462
3258
  }
2463
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: HttpClient, decorators: [{
2464
- type: Injectable,
2465
- args: [{ providedIn: "root" }]
2466
- }], ctorParameters: () => [] });
3259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FooterComponent, decorators: [{
3260
+ type: Component,
3261
+ args: [{
3262
+ selector: 'app-footer',
3263
+ template: `
3264
+ <div class="layout-footer">
3265
+ <div class="flex justify-center flex-1">
3266
+ <div class="mr-2 -my-0.5">
3267
+ <ng-container
3268
+ *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 18, height: 18 }"></ng-container>
3269
+ </div>
3270
+ <span class="font-medium">{{ 'app-info.footer' | translate }}</span>
3271
+ </div>
3272
+ <p class="mr-auto">{{ 'app-info.version' | translate }}</p>
3273
+ </div>
3274
+ `,
3275
+ standalone: true,
3276
+ imports: [TranslatePipe, NgComponentOutlet]
3277
+ }]
3278
+ }] });
2467
3279
 
2468
3280
  /* eslint-disable */
2469
3281
  /* tslint:disable */
@@ -2530,9 +3342,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2530
3342
  type: Injectable
2531
3343
  }] });
2532
3344
 
2533
- class MenuService extends BaseDataService {
3345
+ class MenuService {
2534
3346
  constructor() {
2535
- super(...arguments);
2536
3347
  this.menuSource = new Subject();
2537
3348
  this.resetSource = new Subject();
2538
3349
  this.titleService = inject(AppTitleService);
@@ -2552,7 +3363,9 @@ class MenuService extends BaseDataService {
2552
3363
  */
2553
3364
  onMenuStateChange(event) {
2554
3365
  this.menuSource.next(event);
2555
- this.titleService.setTitle(event.item.label);
3366
+ if (!event.item.items?.length) {
3367
+ this.titleService.setTitle(event.item.label);
3368
+ }
2556
3369
  }
2557
3370
  reset() {
2558
3371
  this.resetSource.next(true);
@@ -2575,7 +3388,7 @@ class MenuService extends BaseDataService {
2575
3388
  editModuleInstance(item) {
2576
3389
  return this.menuDataService.editModuleInstance(item);
2577
3390
  }
2578
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
3391
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2579
3392
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuService }); }
2580
3393
  }
2581
3394
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuService, decorators: [{
@@ -2592,17 +3405,15 @@ class MenuItemComponent {
2592
3405
  this.confirmationService = inject(ConfirmationService);
2593
3406
  this.msgService = inject(MsgService);
2594
3407
  this.menuDataService = inject(MenuApi);
3408
+ this.securityService = inject(SecurityService);
2595
3409
  this.active = false;
2596
3410
  this.subscriptions = [];
2597
3411
  this.localization = {};
2598
3412
  this.key = '';
2599
3413
  this.subscriptions.push(this.menuService.menuSource$.subscribe((value) => {
2600
3414
  Promise.resolve(null).then(() => {
2601
- if (value.routeEvent) {
2602
- this.active = value.key === this.key || value.key.startsWith(this.key + '-');
2603
- }
2604
- else if (value.key !== this.key && !value.key.startsWith(this.key + '-')) {
2605
- this.active = false;
3415
+ if (value.routeEvent && (value.key === this.key || value.key.startsWith(this.key + '-'))) {
3416
+ this.active = true;
2606
3417
  }
2607
3418
  });
2608
3419
  }));
@@ -2655,8 +3466,8 @@ class MenuItemComponent {
2655
3466
  }
2656
3467
  this.menuService.onMenuStateChange({ key: this.key, item: this.item });
2657
3468
  }
2658
- get submenuAnimation() {
2659
- return this.root || this.active ? 'expanded' : 'collapsed';
3469
+ get isSubmenuExpanded() {
3470
+ return this.root || this.active;
2660
3471
  }
2661
3472
  get activeClass() {
2662
3473
  return this.active && !this.root;
@@ -2668,6 +3479,11 @@ class MenuItemComponent {
2668
3479
  this.menuItemCreateDialogComponent.showDialog();
2669
3480
  }
2670
3481
  onContextMenu($event, item) {
3482
+ if (!this.securityService.isAdmin()) {
3483
+ return;
3484
+ }
3485
+ $event.stopPropagation();
3486
+ $event.preventDefault();
2671
3487
  this.menuService.contextMenuItem = item;
2672
3488
  this.contextMenu.model = [
2673
3489
  {
@@ -2787,19 +3603,32 @@ class MenuItemComponent {
2787
3603
  hasVisiblePrev(currentItem) {
2788
3604
  const items = this.getItems(currentItem);
2789
3605
  const currentIndex = items.findIndex((item) => item.moduleInstanceId == currentItem.moduleInstanceId);
2790
- return currentIndex > 0;
3606
+ return this.findPrevVisibleItem(items, currentIndex) !== -1;
2791
3607
  }
2792
3608
  getItems(currentItem) {
2793
- return !currentItem.parentId
2794
- ? this.menuService.menu
2795
- : this.menuService.menu.find((m) => m.moduleInstanceId == currentItem.parentId).items;
3609
+ if (!currentItem.parentId) {
3610
+ return this.menuService.menu;
3611
+ }
3612
+ return this.findItemByModuleInstanceId(this.menuService.menu, currentItem.parentId)?.items ?? [];
3613
+ }
3614
+ findItemByModuleInstanceId(items, moduleInstanceId) {
3615
+ for (const item of items) {
3616
+ if (item.moduleInstanceId == moduleInstanceId) {
3617
+ return item;
3618
+ }
3619
+ const foundItem = this.findItemByModuleInstanceId(item.items ?? [], moduleInstanceId);
3620
+ if (foundItem) {
3621
+ return foundItem;
3622
+ }
3623
+ }
3624
+ return null;
2796
3625
  }
2797
3626
  hasVisibleNext(currentItem) {
2798
3627
  const items = this.getItems(currentItem);
2799
3628
  const currentIndex = items.findIndex((item) => item.moduleInstanceId == currentItem.moduleInstanceId);
2800
- return currentIndex < items.length - 1;
3629
+ return this.findNextVisibleIndex(items, currentIndex) !== -1;
2801
3630
  }
2802
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuItemComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$3.Router }, { token: MenuService }], target: i0.ɵɵFactoryTarget.Component }); }
3631
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuItemComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$4.Router }, { token: MenuService }], target: i0.ɵɵFactoryTarget.Component }); }
2803
3632
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MenuItemComponent, isStandalone: true, selector: "[app-menuitem]", inputs: { item: "item", index: "index", root: "root", parentKey: "parentKey", menuItemCreateDialogComponent: "menuItemCreateDialogComponent", menuItemEditDialogComponent: "menuItemEditDialogComponent", contextMenu: "contextMenu" }, host: { properties: { "class.layout-root-menuitem": "this.root", "class.active-menuitem": "this.activeClass" } }, providers: [ConfirmationService], ngImport: i0, template: `
2804
3633
  <ng-container>
2805
3634
  <p-confirm-dialog />
@@ -2815,7 +3644,8 @@ class MenuItemComponent {
2815
3644
  [attr.href]="item.url"
2816
3645
  [attr.target]="item.target"
2817
3646
  [ngClass]="item.class"
2818
- (click)="itemClick($event)">
3647
+ (click)="itemClick($event)"
3648
+ (contextmenu)="onContextMenu($event, item)">
2819
3649
  <i class="layout-menuitem-icon" [ngClass]="item.icon"></i>
2820
3650
  <span class="layout-menuitem-text">{{ item.label }}</span>
2821
3651
  @if (item.items) {
@@ -2857,7 +3687,7 @@ class MenuItemComponent {
2857
3687
  }
2858
3688
 
2859
3689
  @if (item.items && item.visible !== false) {
2860
- <ul [@children]="submenuAnimation" (contextmenu)="onContextMenu($event, item)">
3690
+ <ul class="layout-submenu" [class.layout-submenu-expanded]="isSubmenuExpanded">
2861
3691
  @for (child of item.items; track child; let i = $index) {
2862
3692
  <li
2863
3693
  app-menuitem
@@ -2872,17 +3702,7 @@ class MenuItemComponent {
2872
3702
  </ul>
2873
3703
  }
2874
3704
  </ng-container>
2875
- `, isInline: true, dependencies: [{ kind: "component", type: MenuItemComponent, selector: "[app-menuitem]", inputs: ["item", "index", "root", "parentKey", "menuItemCreateDialogComponent", "menuItemEditDialogComponent", "contextMenu"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i3$2.Ripple, selector: "[pRipple]" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }], animations: [
2876
- trigger('children', [
2877
- state('collapsed', style({
2878
- height: '0'
2879
- })),
2880
- state('expanded', style({
2881
- height: '*'
2882
- })),
2883
- transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
2884
- ])
2885
- ] }); }
3705
+ `, isInline: true, dependencies: [{ kind: "component", type: MenuItemComponent, selector: "[app-menuitem]", inputs: ["item", "index", "root", "parentKey", "menuItemCreateDialogComponent", "menuItemEditDialogComponent", "contextMenu"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i3$3.Ripple, selector: "[pRipple]" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }] }); }
2886
3706
  }
2887
3707
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuItemComponent, decorators: [{
2888
3708
  type: Component,
@@ -2904,7 +3724,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2904
3724
  [attr.href]="item.url"
2905
3725
  [attr.target]="item.target"
2906
3726
  [ngClass]="item.class"
2907
- (click)="itemClick($event)">
3727
+ (click)="itemClick($event)"
3728
+ (contextmenu)="onContextMenu($event, item)">
2908
3729
  <i class="layout-menuitem-icon" [ngClass]="item.icon"></i>
2909
3730
  <span class="layout-menuitem-text">{{ item.label }}</span>
2910
3731
  @if (item.items) {
@@ -2946,7 +3767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2946
3767
  }
2947
3768
 
2948
3769
  @if (item.items && item.visible !== false) {
2949
- <ul [@children]="submenuAnimation" (contextmenu)="onContextMenu($event, item)">
3770
+ <ul class="layout-submenu" [class.layout-submenu-expanded]="isSubmenuExpanded">
2950
3771
  @for (child of item.items; track child; let i = $index) {
2951
3772
  <li
2952
3773
  app-menuitem
@@ -2962,21 +3783,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2962
3783
  }
2963
3784
  </ng-container>
2964
3785
  `,
2965
- animations: [
2966
- trigger('children', [
2967
- state('collapsed', style({
2968
- height: '0'
2969
- })),
2970
- state('expanded', style({
2971
- height: '*'
2972
- })),
2973
- transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
2974
- ])
2975
- ],
2976
3786
  imports: [RippleModule, NgClass, RouterLinkActive, RouterLink, ContextMenuModule, ConfirmDialog],
2977
3787
  providers: [ConfirmationService]
2978
3788
  }]
2979
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$3.Router }, { type: MenuService }], propDecorators: { item: [{
3789
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$4.Router }, { type: MenuService }], propDecorators: { item: [{
2980
3790
  type: Input
2981
3791
  }], index: [{
2982
3792
  type: Input
@@ -3002,9 +3812,21 @@ class MenuItemCreateDialogComponent {
3002
3812
  constructor() {
3003
3813
  this.menuService = inject(MenuService);
3004
3814
  this.menu = inject(MenuApi);
3815
+ this.msgService = inject(MsgService);
3005
3816
  this.visibleChange = new EventEmitter();
3006
3817
  this.modules = [];
3818
+ this.iconOptions = Object.values(PrimeIcons)
3819
+ .filter((icon) => typeof icon === 'string')
3820
+ .map((icon) => ({
3821
+ label: icon.replace('pi pi-', ''),
3822
+ value: icon
3823
+ }))
3824
+ .sort((left, right) => left.label.localeCompare(right.label));
3007
3825
  this.selectIcon = 'pi pi-box';
3826
+ this.saving = false;
3827
+ }
3828
+ get canSave() {
3829
+ return !this.saving && !!this.selectModule;
3008
3830
  }
3009
3831
  async ngOnInit() {
3010
3832
  this.modules = await this.menu.getModules();
@@ -3014,15 +3836,30 @@ class MenuItemCreateDialogComponent {
3014
3836
  this.visibleChange.emit(this.visible);
3015
3837
  }
3016
3838
  async save() {
3839
+ if (this.saving) {
3840
+ return;
3841
+ }
3842
+ if (!this.selectModule) {
3843
+ return;
3844
+ }
3017
3845
  const item = {
3018
3846
  moduleId: this.selectModule,
3019
3847
  label: this.label,
3020
3848
  icon: this.selectIcon,
3021
3849
  parentId: this.menuService.contextMenuItem?.moduleInstanceId
3022
3850
  };
3023
- await this.menuService.addModuleInstance(item);
3024
- await this.menuService.loadMenu();
3025
- this.hide();
3851
+ this.saving = true;
3852
+ try {
3853
+ await this.menuService.addModuleInstance(item);
3854
+ await this.menuService.loadMenu();
3855
+ this.hide();
3856
+ }
3857
+ catch (error) {
3858
+ this.msgService.errorFromException(error, 'Unexpected error');
3859
+ }
3860
+ finally {
3861
+ this.saving = false;
3862
+ }
3026
3863
  }
3027
3864
  hide() {
3028
3865
  this.visible = false;
@@ -3038,7 +3875,8 @@ class MenuItemCreateDialogComponent {
3038
3875
  header="{{ 'menuItemCreateDialogComponent.header' | translate }}"
3039
3876
  [modal]="true"
3040
3877
  [style]="{ width: '40rem' }"
3041
- [(visible)]="visible">
3878
+ [(visible)]="visible"
3879
+ (keydown.enter)="save()">
3042
3880
  @if (menuService.contextMenuItem) {
3043
3881
  <div class="flex items-center gap-4 mb-4 mt-1">
3044
3882
  <label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-parent-input">
@@ -3077,24 +3915,47 @@ class MenuItemCreateDialogComponent {
3077
3915
  <label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-icon">
3078
3916
  {{ 'menuItemCreateDialogComponent.icon' | translate }}
3079
3917
  </label>
3080
- <i class="{{ selectIcon }}"></i>
3081
- <input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon" />
3918
+ <p-select
3919
+ appendTo="body"
3920
+ class="flex-auto"
3921
+ filterBy="label,value"
3922
+ id="oip-menu-item-create-dialog-icon"
3923
+ optionLabel="label"
3924
+ optionValue="value"
3925
+ scrollHeight="18rem"
3926
+ [filter]="true"
3927
+ [options]="iconOptions"
3928
+ [(ngModel)]="selectIcon">
3929
+ <ng-template let-icon pTemplate="selectedItem">
3930
+ <div class="flex items-center gap-2">
3931
+ <i [class]="icon.value"></i>
3932
+ <span>{{ icon.label }}</span>
3933
+ </div>
3934
+ </ng-template>
3935
+ <ng-template let-icon pTemplate="item">
3936
+ <div class="flex items-center gap-2">
3937
+ <i [class]="icon.value"></i>
3938
+ <span>{{ icon.label }}</span>
3939
+ </div>
3940
+ </ng-template>
3941
+ </p-select>
3082
3942
  </div>
3083
3943
  <div class="flex justify-end gap-2">
3084
3944
  <p-button
3085
3945
  id="oip-menu-item-create-cancel"
3086
3946
  label="{{ 'menuItemCreateDialogComponent.cancel' | translate }}"
3087
3947
  severity="secondary"
3088
- (click)="changeVisible()"
3089
- (keydown)="changeVisible()" />
3948
+ [disabled]="saving"
3949
+ (click)="changeVisible()" />
3090
3950
  <p-button
3091
3951
  id="oip-menu-item-create-save"
3092
3952
  label="{{ 'menuItemCreateDialogComponent.save' | translate }}"
3093
- (click)="save()"
3094
- (keydown)="save()" />
3953
+ [disabled]="!canSave"
3954
+ [loading]="saving"
3955
+ (click)="save()" />
3095
3956
  </div>
3096
3957
  </p-dialog>
3097
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2$3.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3958
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3$4.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3098
3959
  }
3099
3960
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuItemCreateDialogComponent, decorators: [{
3100
3961
  type: Component,
@@ -3107,7 +3968,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3107
3968
  header="{{ 'menuItemCreateDialogComponent.header' | translate }}"
3108
3969
  [modal]="true"
3109
3970
  [style]="{ width: '40rem' }"
3110
- [(visible)]="visible">
3971
+ [(visible)]="visible"
3972
+ (keydown.enter)="save()">
3111
3973
  @if (menuService.contextMenuItem) {
3112
3974
  <div class="flex items-center gap-4 mb-4 mt-1">
3113
3975
  <label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-parent-input">
@@ -3146,21 +4008,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3146
4008
  <label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-icon">
3147
4009
  {{ 'menuItemCreateDialogComponent.icon' | translate }}
3148
4010
  </label>
3149
- <i class="{{ selectIcon }}"></i>
3150
- <input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon" />
4011
+ <p-select
4012
+ appendTo="body"
4013
+ class="flex-auto"
4014
+ filterBy="label,value"
4015
+ id="oip-menu-item-create-dialog-icon"
4016
+ optionLabel="label"
4017
+ optionValue="value"
4018
+ scrollHeight="18rem"
4019
+ [filter]="true"
4020
+ [options]="iconOptions"
4021
+ [(ngModel)]="selectIcon">
4022
+ <ng-template let-icon pTemplate="selectedItem">
4023
+ <div class="flex items-center gap-2">
4024
+ <i [class]="icon.value"></i>
4025
+ <span>{{ icon.label }}</span>
4026
+ </div>
4027
+ </ng-template>
4028
+ <ng-template let-icon pTemplate="item">
4029
+ <div class="flex items-center gap-2">
4030
+ <i [class]="icon.value"></i>
4031
+ <span>{{ icon.label }}</span>
4032
+ </div>
4033
+ </ng-template>
4034
+ </p-select>
3151
4035
  </div>
3152
4036
  <div class="flex justify-end gap-2">
3153
4037
  <p-button
3154
4038
  id="oip-menu-item-create-cancel"
3155
4039
  label="{{ 'menuItemCreateDialogComponent.cancel' | translate }}"
3156
4040
  severity="secondary"
3157
- (click)="changeVisible()"
3158
- (keydown)="changeVisible()" />
4041
+ [disabled]="saving"
4042
+ (click)="changeVisible()" />
3159
4043
  <p-button
3160
4044
  id="oip-menu-item-create-save"
3161
4045
  label="{{ 'menuItemCreateDialogComponent.save' | translate }}"
3162
- (click)="save()"
3163
- (keydown)="save()" />
4046
+ [disabled]="!canSave"
4047
+ [loading]="saving"
4048
+ (click)="save()" />
3164
4049
  </div>
3165
4050
  </p-dialog>
3166
4051
  `
@@ -3174,10 +4059,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3174
4059
  class MenuItemEditDialogComponent {
3175
4060
  constructor() {
3176
4061
  this.menuService = inject(MenuService);
3177
- this.securityDataService = inject(SecurityDataService);
4062
+ this.securityApi = inject(SecurityApi);
4063
+ this.msgService = inject(MsgService);
3178
4064
  this.visibleChange = new EventEmitter();
3179
4065
  this.modules = [];
3180
4066
  this.roles = [];
4067
+ this.iconOptions = Object.values(PrimeIcons)
4068
+ .filter((icon) => typeof icon === 'string')
4069
+ .map((icon) => ({
4070
+ label: icon.replace('pi pi-', ''),
4071
+ value: icon
4072
+ }))
4073
+ .sort((left, right) => left.label.localeCompare(right.label));
3181
4074
  this.item = {
3182
4075
  icon: '',
3183
4076
  label: '',
@@ -3186,15 +4079,28 @@ class MenuItemEditDialogComponent {
3186
4079
  moduleInstanceId: 0,
3187
4080
  parentId: 0
3188
4081
  };
4082
+ this.saving = false;
3189
4083
  }
3190
4084
  changeVisible() {
3191
4085
  this.visible = !this.visible;
3192
4086
  this.visibleChange.emit(this.visible);
3193
4087
  }
3194
4088
  async save() {
3195
- await this.menuService.editModuleInstance(this.item);
3196
- await this.menuService.loadMenu();
3197
- this.hide();
4089
+ if (this.saving) {
4090
+ return;
4091
+ }
4092
+ this.saving = true;
4093
+ try {
4094
+ await this.menuService.editModuleInstance(this.item);
4095
+ await this.menuService.loadMenu();
4096
+ this.hide();
4097
+ }
4098
+ catch (error) {
4099
+ this.msgService.error(error);
4100
+ }
4101
+ finally {
4102
+ this.saving = false;
4103
+ }
3198
4104
  }
3199
4105
  hide() {
3200
4106
  this.visible = false;
@@ -3209,7 +4115,7 @@ class MenuItemEditDialogComponent {
3209
4115
  icon: this.menuService.contextMenuItem?.icon,
3210
4116
  viewRoles: this.menuService.contextMenuItem?.securities
3211
4117
  };
3212
- this.roles = await this.securityDataService.getRealmRoles();
4118
+ this.roles = await this.securityApi.getRealmRoles();
3213
4119
  this.menuService.getModules().then((data) => {
3214
4120
  this.modules = data;
3215
4121
  });
@@ -3222,7 +4128,8 @@ class MenuItemEditDialogComponent {
3222
4128
  header="{{ 'menuItemEditDialogComponent.header' | translate }}"
3223
4129
  [modal]="true"
3224
4130
  [style]="{ width: '40rem' }"
3225
- [(visible)]="visible">
4131
+ [(visible)]="visible"
4132
+ (keydown.enter)="save()">
3226
4133
  <div class="flex items-center gap-4 mb-4 mt-1">
3227
4134
  <label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-menu-input">
3228
4135
  {{ 'menuItemEditDialogComponent.label' | translate }}
@@ -3239,8 +4146,30 @@ class MenuItemEditDialogComponent {
3239
4146
  <label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-icon">
3240
4147
  {{ 'menuItemEditDialogComponent.icon' | translate }}
3241
4148
  </label>
3242
- <i class="{{ item.icon }}"></i>
3243
- <input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon" />
4149
+ <p-select
4150
+ appendTo="body"
4151
+ class="flex-auto"
4152
+ filterBy="label,value"
4153
+ id="oip-menu-item-edit-dialog-icon"
4154
+ optionLabel="label"
4155
+ optionValue="value"
4156
+ scrollHeight="18rem"
4157
+ [filter]="true"
4158
+ [options]="iconOptions"
4159
+ [(ngModel)]="item.icon">
4160
+ <ng-template let-icon pTemplate="selectedItem">
4161
+ <div class="flex items-center gap-2">
4162
+ <i [class]="icon.value"></i>
4163
+ <span>{{ icon.label }}</span>
4164
+ </div>
4165
+ </ng-template>
4166
+ <ng-template let-icon pTemplate="item">
4167
+ <div class="flex items-center gap-2">
4168
+ <i [class]="icon.value"></i>
4169
+ <span>{{ icon.label }}</span>
4170
+ </div>
4171
+ </ng-template>
4172
+ </p-select>
3244
4173
  </div>
3245
4174
 
3246
4175
  <div class="flex items-center gap-4 mb-4">
@@ -3262,21 +4191,22 @@ class MenuItemEditDialogComponent {
3262
4191
  id="oip-menu-item-edit-dialog-cancel-edit-button"
3263
4192
  label="{{ 'menuItemEditDialogComponent.cancel' | translate }}"
3264
4193
  severity="secondary"
3265
- (click)="changeVisible()"
3266
- (keydown)="changeVisible()" />
4194
+ [disabled]="saving"
4195
+ (click)="changeVisible()" />
3267
4196
  <p-button
3268
4197
  id="oip-menu-item-edit-dialog-save-edit-button"
3269
4198
  label="{{ 'menuItemEditDialogComponent.save' | translate }}"
3270
- (click)="save()"
3271
- (keydown)="save()" />
4199
+ [disabled]="saving"
4200
+ [loading]="saving"
4201
+ (click)="save()" />
3272
4202
  </div>
3273
4203
  </p-dialog>
3274
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2$3.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i1.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4204
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3$4.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i1.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3275
4205
  }
3276
4206
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuItemEditDialogComponent, decorators: [{
3277
4207
  type: Component,
3278
4208
  args: [{
3279
- imports: [ButtonModule, DialogModule, InputTextModule, FormsModule, TranslatePipe, MultiSelectModule],
4209
+ imports: [ButtonModule, DialogModule, InputTextModule, SelectModule, FormsModule, TranslatePipe, MultiSelectModule],
3280
4210
  selector: 'menu-item-edit-dialog',
3281
4211
  standalone: true,
3282
4212
  template: `
@@ -3284,7 +4214,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3284
4214
  header="{{ 'menuItemEditDialogComponent.header' | translate }}"
3285
4215
  [modal]="true"
3286
4216
  [style]="{ width: '40rem' }"
3287
- [(visible)]="visible">
4217
+ [(visible)]="visible"
4218
+ (keydown.enter)="save()">
3288
4219
  <div class="flex items-center gap-4 mb-4 mt-1">
3289
4220
  <label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-menu-input">
3290
4221
  {{ 'menuItemEditDialogComponent.label' | translate }}
@@ -3301,8 +4232,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3301
4232
  <label class="font-semibold w-1/3" for="oip-menu-item-edit-dialog-icon">
3302
4233
  {{ 'menuItemEditDialogComponent.icon' | translate }}
3303
4234
  </label>
3304
- <i class="{{ item.icon }}"></i>
3305
- <input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon" />
4235
+ <p-select
4236
+ appendTo="body"
4237
+ class="flex-auto"
4238
+ filterBy="label,value"
4239
+ id="oip-menu-item-edit-dialog-icon"
4240
+ optionLabel="label"
4241
+ optionValue="value"
4242
+ scrollHeight="18rem"
4243
+ [filter]="true"
4244
+ [options]="iconOptions"
4245
+ [(ngModel)]="item.icon">
4246
+ <ng-template let-icon pTemplate="selectedItem">
4247
+ <div class="flex items-center gap-2">
4248
+ <i [class]="icon.value"></i>
4249
+ <span>{{ icon.label }}</span>
4250
+ </div>
4251
+ </ng-template>
4252
+ <ng-template let-icon pTemplate="item">
4253
+ <div class="flex items-center gap-2">
4254
+ <i [class]="icon.value"></i>
4255
+ <span>{{ icon.label }}</span>
4256
+ </div>
4257
+ </ng-template>
4258
+ </p-select>
3306
4259
  </div>
3307
4260
 
3308
4261
  <div class="flex items-center gap-4 mb-4">
@@ -3324,13 +4277,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3324
4277
  id="oip-menu-item-edit-dialog-cancel-edit-button"
3325
4278
  label="{{ 'menuItemEditDialogComponent.cancel' | translate }}"
3326
4279
  severity="secondary"
3327
- (click)="changeVisible()"
3328
- (keydown)="changeVisible()" />
4280
+ [disabled]="saving"
4281
+ (click)="changeVisible()" />
3329
4282
  <p-button
3330
4283
  id="oip-menu-item-edit-dialog-save-edit-button"
3331
4284
  label="{{ 'menuItemEditDialogComponent.save' | translate }}"
3332
- (click)="save()"
3333
- (keydown)="save()" />
4285
+ [disabled]="saving"
4286
+ [loading]="saving"
4287
+ (click)="save()" />
3334
4288
  </div>
3335
4289
  </p-dialog>
3336
4290
  `
@@ -3354,6 +4308,9 @@ class MenuComponent {
3354
4308
  this.menuItemCreateDialogComponent.showDialog();
3355
4309
  }
3356
4310
  onContextMenu($event) {
4311
+ if (!this.securityService.isAdmin()) {
4312
+ return;
4313
+ }
3357
4314
  this.menuService.contextMenuItem = null;
3358
4315
  this.contextMenu.model = [
3359
4316
  {
@@ -3385,11 +4342,11 @@ class MenuComponent {
3385
4342
  }
3386
4343
  </ul>
3387
4344
  </div>
3388
- <p-contextMenu [target]="empty"/>
3389
4345
  @if (securityService.isAdmin()) {
4346
+ <p-contextMenu [target]="empty"/>
3390
4347
  <menu-item-create-dialog/>
3391
4348
  <menu-item-edit-dialog/>
3392
- }`, isInline: true, dependencies: [{ kind: "component", type: MenuItemComponent, selector: "[app-menuitem]", inputs: ["item", "index", "root", "parentKey", "menuItemCreateDialogComponent", "menuItemEditDialogComponent", "contextMenu"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i1$4.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "component", type: MenuItemCreateDialogComponent, selector: "menu-item-create-dialog", inputs: ["visible"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: MenuItemEditDialogComponent, selector: "menu-item-edit-dialog", inputs: ["visible"], outputs: ["visibleChange"] }] }); }
4349
+ }`, isInline: true, dependencies: [{ kind: "component", type: MenuItemComponent, selector: "[app-menuitem]", inputs: ["item", "index", "root", "parentKey", "menuItemCreateDialogComponent", "menuItemEditDialogComponent", "contextMenu"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i1$5.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "component", type: MenuItemCreateDialogComponent, selector: "menu-item-create-dialog", inputs: ["visible"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: MenuItemEditDialogComponent, selector: "menu-item-edit-dialog", inputs: ["visible"], outputs: ["visibleChange"] }] }); }
3393
4350
  }
3394
4351
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MenuComponent, decorators: [{
3395
4352
  type: Component,
@@ -3428,8 +4385,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3428
4385
  }
3429
4386
  </ul>
3430
4387
  </div>
3431
- <p-contextMenu [target]="empty"/>
3432
4388
  @if (securityService.isAdmin()) {
4389
+ <p-contextMenu [target]="empty"/>
3433
4390
  <menu-item-create-dialog/>
3434
4391
  <menu-item-edit-dialog/>
3435
4392
  }`
@@ -3550,7 +4507,7 @@ class AppLayoutComponent {
3550
4507
  </div>
3551
4508
  <div class="layout-mask animate-fadein"></div>
3552
4509
  </div>
3553
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: SidebarComponent, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$3.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: FooterComponent, selector: "app-footer" }] }); }
4510
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: SidebarComponent, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: FooterComponent, selector: "app-footer" }] }); }
3554
4511
  }
3555
4512
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppLayoutComponent, decorators: [{
3556
4513
  type: Component,
@@ -3617,7 +4574,7 @@ class AppFloatingConfiguratorComponent {
3617
4574
  <app-configurator />
3618
4575
  </div>
3619
4576
  </div>
3620
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }] }); }
4577
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$2.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }] }); }
3621
4578
  }
3622
4579
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppFloatingConfiguratorComponent, decorators: [{
3623
4580
  type: Component,
@@ -3711,70 +4668,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3711
4668
 
3712
4669
  class UnauthorizedComponent {
3713
4670
  constructor() {
3714
- this.securityService = inject(SecurityService);
3715
- }
3716
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3717
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: UnauthorizedComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
3718
- <app-floating-configurator />
3719
- <div
3720
- class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
3721
- <div class="flex flex-col items-center justify-center">
3722
- <div
3723
- style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
3724
- <div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
3725
- <div class="flex flex-col items-center justify-center">
3726
- <logo [height]="96" [width]="96" />
3727
- </div>
3728
- <div class="text-center mb-8">
3729
- <div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4">
3730
- {{ 'unauthorized.welcomeToOip' | translate }}
3731
- </div>
3732
- <span class="text-muted-color font-medium">{{ 'unauthorized.signInToContinue' | translate }}</span>
3733
- </div>
3734
- <div>
3735
- <p-button
3736
- id="oip-unauthorized-error-sign-in-button"
3737
- label="{{ 'unauthorized.signIn' | translate }}"
3738
- styleClass="w-full"
3739
- (click)="securityService.authorize()"></p-button>
3740
- </div>
3741
- </div>
3742
- </div>
3743
- </div>
3744
- </div>
4671
+ this.securityService = inject(SecurityService);
4672
+ this.route = inject(ActivatedRoute);
4673
+ this.router = inject(Router);
4674
+ }
4675
+ signIn() {
4676
+ const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') || '/';
4677
+ this.router.navigate(['/unauthorized'], {
4678
+ queryParams: { returnUrl },
4679
+ replaceUrl: true
4680
+ }).then(() => this.securityService.authorize());
4681
+ }
4682
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4683
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: UnauthorizedComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
4684
+ <app-floating-configurator />
4685
+ <div
4686
+ class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
4687
+ <div class="flex flex-col items-center justify-center">
4688
+ <div
4689
+ style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
4690
+ <div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
4691
+ <div class="flex flex-col items-center justify-center">
4692
+ <logo [height]="96" [width]="96" />
4693
+ </div>
4694
+ <div class="text-center mb-8">
4695
+ <div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4">
4696
+ {{ 'unauthorized.welcomeToOip' | translate }}
4697
+ </div>
4698
+ <span class="text-muted-color font-medium">{{ 'unauthorized.signInToContinue' | translate }}</span>
4699
+ </div>
4700
+ <div>
4701
+ <p-button
4702
+ id="oip-unauthorized-error-sign-in-button"
4703
+ label="{{ 'unauthorized.signIn' | translate }}"
4704
+ styleClass="w-full"
4705
+ (click)="signIn()"></p-button>
4706
+ </div>
4707
+ </div>
4708
+ </div>
4709
+ </div>
4710
+ </div>
3745
4711
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RippleModule }, { kind: "component", type: LogoComponent, selector: "logo", inputs: ["width", "height"] }, { kind: "component", type: AppFloatingConfiguratorComponent, selector: "app-floating-configurator" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3746
4712
  }
3747
4713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UnauthorizedComponent, decorators: [{
3748
4714
  type: Component,
3749
4715
  args: [{
3750
- template: `
3751
- <app-floating-configurator />
3752
- <div
3753
- class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
3754
- <div class="flex flex-col items-center justify-center">
3755
- <div
3756
- style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
3757
- <div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
3758
- <div class="flex flex-col items-center justify-center">
3759
- <logo [height]="96" [width]="96" />
3760
- </div>
3761
- <div class="text-center mb-8">
3762
- <div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4">
3763
- {{ 'unauthorized.welcomeToOip' | translate }}
3764
- </div>
3765
- <span class="text-muted-color font-medium">{{ 'unauthorized.signInToContinue' | translate }}</span>
3766
- </div>
3767
- <div>
3768
- <p-button
3769
- id="oip-unauthorized-error-sign-in-button"
3770
- label="{{ 'unauthorized.signIn' | translate }}"
3771
- styleClass="w-full"
3772
- (click)="securityService.authorize()"></p-button>
3773
- </div>
3774
- </div>
3775
- </div>
3776
- </div>
3777
- </div>
4716
+ template: `
4717
+ <app-floating-configurator />
4718
+ <div
4719
+ class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
4720
+ <div class="flex flex-col items-center justify-center">
4721
+ <div
4722
+ style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
4723
+ <div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
4724
+ <div class="flex flex-col items-center justify-center">
4725
+ <logo [height]="96" [width]="96" />
4726
+ </div>
4727
+ <div class="text-center mb-8">
4728
+ <div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4">
4729
+ {{ 'unauthorized.welcomeToOip' | translate }}
4730
+ </div>
4731
+ <span class="text-muted-color font-medium">{{ 'unauthorized.signInToContinue' | translate }}</span>
4732
+ </div>
4733
+ <div>
4734
+ <p-button
4735
+ id="oip-unauthorized-error-sign-in-button"
4736
+ label="{{ 'unauthorized.signIn' | translate }}"
4737
+ styleClass="w-full"
4738
+ (click)="signIn()"></p-button>
4739
+ </div>
4740
+ </div>
4741
+ </div>
4742
+ </div>
4743
+ </div>
3778
4744
  `,
3779
4745
  imports: [
3780
4746
  ButtonModule,
@@ -3819,7 +4785,7 @@ class ErrorComponent {
3819
4785
  </div>
3820
4786
  </div>
3821
4787
  </div>
3822
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i3$2.Ripple, selector: "[pRipple]" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
4788
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i3$3.Ripple, selector: "[pRipple]" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
3823
4789
  }
3824
4790
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ErrorComponent, decorators: [{
3825
4791
  type: Component,
@@ -3892,7 +4858,7 @@ class ProfileComponent {
3892
4858
  [auto]="true"
3893
4859
  (onUpload)="onBasicUploadAuto($event)" />
3894
4860
  </div>
3895
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i1$5.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: ImageModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4861
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i1$6.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: ImageModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3896
4862
  }
3897
4863
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComponent, decorators: [{
3898
4864
  type: Component,
@@ -3932,7 +4898,6 @@ class ConfigComponent {
3932
4898
  this.userService = inject(UserService);
3933
4899
  this.securityService = inject(SecurityService);
3934
4900
  this.menuService = inject(MenuService);
3935
- this.l10n = {};
3936
4901
  this.dateFormats = ['dd.MM.yyyy', 'dd.MM.yy', 'yyyy-MM-dd', 'dd.MMM.yyyy'];
3937
4902
  this.timeFormats = ['HH:mm:ss', 'HH:mm'];
3938
4903
  // @ts-ignore
@@ -3963,204 +4928,204 @@ class ConfigComponent {
3963
4928
  await this.menuService.loadMenu();
3964
4929
  }
3965
4930
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3966
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfigComponent, isStandalone: true, selector: "app-config", ngImport: i0, template: `
3967
- <div class="flex flex-col md:flex-row gap-4">
3968
- <div class="md:w-1/2">
3969
- <div class="card flex flex-col gap-4">
3970
- <div class="font-semibold text-xl">{{ 'config.profile' | translate }}</div>
3971
- <div class="flex justify-content-end flex-wrap">
3972
- {{ userService.userName }}
3973
- </div>
3974
- <label>
3975
- {{ 'config.photo' | translate }}
3976
- <span
3977
- class="pi pi-question-circle"
3978
- pTooltip="{{ 'config.usePhoto256x256Pixel' | translate }}"
3979
- tooltipPosition="right"></span>
3980
- </label>
3981
- <div class="flex justify-content-end flex-wrap">
3982
- <user-profile></user-profile>
3983
- </div>
3984
- </div>
3985
- </div>
3986
- <div class="md:w-1/2">
3987
- <div class="card flex flex-col gap-4">
3988
- <div class="font-semibold text-xl">{{ 'config.localization' | translate }}</div>
3989
- <label> {{ 'config.selectLanguage' | translate }} </label>
3990
- <div class="flex justify-content-end flex-wrap">
3991
- <p-select
3992
- class="w-full md:w-56"
3993
- optionLabel="name"
3994
- optionValue="code"
3995
- qa-id="oip-app-config-language-select"
3996
- [options]="l10nService.availableLanguages"
3997
- [(ngModel)]="selectedLanguage"
3998
- (onChange)="changeLanguage()">
3999
- <ng-template #selectedItem let-selectedOption>
4000
- <div class="flex items-center gap-2">
4001
- <span [class]="selectedOption.icon"></span>
4002
- <div>{{ selectedOption.name }}</div>
4003
- </div>
4004
- </ng-template>
4005
- <ng-template #item let-languages>
4006
- <div class="flex items-center gap-2">
4007
- <span [class]="languages.icon"></span>
4008
- <div>{{ languages.name }}</div>
4009
- </div>
4010
- </ng-template>
4011
- </p-select>
4012
- </div>
4013
- <div class="flex flex-col gap-5">
4014
- <div class="mt-5">{{ 'config.dateTimeFormat' | translate }}</div>
4015
- <p-select
4016
- class="w-full md:w-56"
4017
- qa-id="oip-app-config-date-format-select"
4018
- [options]="dateFormats"
4019
- [placeholder]="'config.dateFormat' | translate"
4020
- [(ngModel)]="selectedDateFormat"
4021
- (onChange)="changeDateFormat()" />
4022
- <p-select
4023
- class="w-full md:w-56"
4024
- qa-id="oip-app-config-time-format-select"
4025
- [options]="timeFormats"
4026
- [placeholder]="'config.timeFormat' | translate"
4027
- [(ngModel)]="selectedTimeFormat"
4028
- (onChange)="changeTimeFormat()" />
4029
- <div class="mt-5">{{ 'config.timeZone' | translate }}</div>
4030
- <p-select
4031
- class="w-full md:w-56"
4032
- qa-id="oip-app-config-timezone-select"
4033
- [filter]="true"
4034
- [options]="allTimeZones"
4035
- [placeholder]="'config.timeZone' | translate"
4036
- [virtualScroll]="true"
4037
- [virtualScrollItemSize]="34"
4038
- [(ngModel)]="selectedTimeZone"
4039
- (onChange)="changeTimeZone()" />
4040
- </div>
4041
- </div>
4042
- </div>
4043
- @if (securityService.isAdmin()) {
4044
- <div class="md:w-1/2">
4045
- <div class="card flex flex-col gap-4">
4046
- <div class="font-semibold text-xl">{{ 'config.menu' | translate }}</div>
4047
- <div class="flex items-center gap-2">
4048
- <label for="oip-app-config-admin-mode">{{ 'config.all' | translate }}</label>
4049
- <p-toggle-switch
4050
- id="oip-app-config-admin-mode"
4051
- [(ngModel)]="menuService.adminMode"
4052
- (onChange)="onSwitchChange()"></p-toggle-switch>
4053
- </div>
4054
- <div class="flex items-center gap-2">
4055
- <label for="oip-app-config-admin-mode">{{ 'config.moduleManagement' | translate }}</label>
4056
- <p-button icon="pi pi-cog" label="{{ 'config.goTo' | translate }}" routerLink="/modules" />
4057
- </div>
4058
- </div>
4059
- </div>
4060
- }
4061
- </div>
4931
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfigComponent, isStandalone: true, selector: "app-config", ngImport: i0, template: `
4932
+ <div class="flex flex-col md:flex-row gap-4">
4933
+ <div class="md:w-1/2">
4934
+ <div class="card flex flex-col gap-4">
4935
+ <div class="font-semibold text-xl">{{ 'config.profile' | translate }}</div>
4936
+ <div class="flex justify-content-end flex-wrap">
4937
+ {{ userService.userName }}
4938
+ </div>
4939
+ <label>
4940
+ {{ 'config.photo' | translate }}
4941
+ <span
4942
+ class="pi pi-question-circle"
4943
+ pTooltip="{{ 'config.usePhoto256x256Pixel' | translate }}"
4944
+ tooltipPosition="right"></span>
4945
+ </label>
4946
+ <div class="flex justify-content-end flex-wrap">
4947
+ <user-profile></user-profile>
4948
+ </div>
4949
+ </div>
4950
+ </div>
4951
+ <div class="md:w-1/2">
4952
+ <div class="card flex flex-col gap-4">
4953
+ <div class="font-semibold text-xl">{{ 'config.localization' | translate }}</div>
4954
+ <label> {{ 'config.selectLanguage' | translate }} </label>
4955
+ <div class="flex justify-content-end flex-wrap">
4956
+ <p-select
4957
+ class="w-full md:w-56"
4958
+ optionLabel="name"
4959
+ optionValue="code"
4960
+ qa-id="oip-app-config-language-select"
4961
+ [options]="l10nService.availableLanguages"
4962
+ [(ngModel)]="selectedLanguage"
4963
+ (onChange)="changeLanguage()">
4964
+ <ng-template #selectedItem let-selectedOption>
4965
+ <div class="flex items-center gap-2">
4966
+ <span [class]="selectedOption.icon"></span>
4967
+ <div>{{ selectedOption.name }}</div>
4968
+ </div>
4969
+ </ng-template>
4970
+ <ng-template #item let-languages>
4971
+ <div class="flex items-center gap-2">
4972
+ <span [class]="languages.icon"></span>
4973
+ <div>{{ languages.name }}</div>
4974
+ </div>
4975
+ </ng-template>
4976
+ </p-select>
4977
+ </div>
4978
+ <div class="flex flex-col gap-5">
4979
+ <div class="mt-5">{{ 'config.dateTimeFormat' | translate }}</div>
4980
+ <p-select
4981
+ class="w-full md:w-56"
4982
+ qa-id="oip-app-config-date-format-select"
4983
+ [options]="dateFormats"
4984
+ [placeholder]="'config.dateFormat' | translate"
4985
+ [(ngModel)]="selectedDateFormat"
4986
+ (onChange)="changeDateFormat()" />
4987
+ <p-select
4988
+ class="w-full md:w-56"
4989
+ qa-id="oip-app-config-time-format-select"
4990
+ [options]="timeFormats"
4991
+ [placeholder]="'config.timeFormat' | translate"
4992
+ [(ngModel)]="selectedTimeFormat"
4993
+ (onChange)="changeTimeFormat()" />
4994
+ <div class="mt-5">{{ 'config.timeZone' | translate }}</div>
4995
+ <p-select
4996
+ class="w-full md:w-56"
4997
+ qa-id="oip-app-config-timezone-select"
4998
+ [filter]="true"
4999
+ [options]="allTimeZones"
5000
+ [placeholder]="'config.timeZone' | translate"
5001
+ [virtualScroll]="true"
5002
+ [virtualScrollItemSize]="34"
5003
+ [(ngModel)]="selectedTimeZone"
5004
+ (onChange)="changeTimeZone()" />
5005
+ </div>
5006
+ </div>
5007
+ </div>
5008
+ @if (securityService.isAdmin()) {
5009
+ <div class="md:w-1/2">
5010
+ <div class="card flex flex-col gap-4">
5011
+ <div class="font-semibold text-xl">{{ 'config.menu' | translate }}</div>
5012
+ <div class="flex items-center gap-2">
5013
+ <label for="oip-app-config-admin-mode">{{ 'config.all' | translate }}</label>
5014
+ <p-toggle-switch
5015
+ id="oip-app-config-admin-mode"
5016
+ [(ngModel)]="menuService.adminMode"
5017
+ (onChange)="onSwitchChange()"></p-toggle-switch>
5018
+ </div>
5019
+ <div class="flex items-center gap-2">
5020
+ <label for="oip-app-config-admin-mode">{{ 'config.moduleManagement' | translate }}</label>
5021
+ <p-button icon="pi pi-cog" label="{{ 'config.goTo' | translate }}" routerLink="/modules" />
5022
+ </div>
5023
+ </div>
5024
+ </div>
5025
+ }
5026
+ </div>
4062
5027
  `, isInline: true, dependencies: [{ kind: "component", type: ProfileComponent, selector: "user-profile" }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TableModule }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i2$5.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4063
5028
  }
4064
5029
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfigComponent, decorators: [{
4065
5030
  type: Component,
4066
5031
  args: [{
4067
5032
  selector: 'app-config',
4068
- template: `
4069
- <div class="flex flex-col md:flex-row gap-4">
4070
- <div class="md:w-1/2">
4071
- <div class="card flex flex-col gap-4">
4072
- <div class="font-semibold text-xl">{{ 'config.profile' | translate }}</div>
4073
- <div class="flex justify-content-end flex-wrap">
4074
- {{ userService.userName }}
4075
- </div>
4076
- <label>
4077
- {{ 'config.photo' | translate }}
4078
- <span
4079
- class="pi pi-question-circle"
4080
- pTooltip="{{ 'config.usePhoto256x256Pixel' | translate }}"
4081
- tooltipPosition="right"></span>
4082
- </label>
4083
- <div class="flex justify-content-end flex-wrap">
4084
- <user-profile></user-profile>
4085
- </div>
4086
- </div>
4087
- </div>
4088
- <div class="md:w-1/2">
4089
- <div class="card flex flex-col gap-4">
4090
- <div class="font-semibold text-xl">{{ 'config.localization' | translate }}</div>
4091
- <label> {{ 'config.selectLanguage' | translate }} </label>
4092
- <div class="flex justify-content-end flex-wrap">
4093
- <p-select
4094
- class="w-full md:w-56"
4095
- optionLabel="name"
4096
- optionValue="code"
4097
- qa-id="oip-app-config-language-select"
4098
- [options]="l10nService.availableLanguages"
4099
- [(ngModel)]="selectedLanguage"
4100
- (onChange)="changeLanguage()">
4101
- <ng-template #selectedItem let-selectedOption>
4102
- <div class="flex items-center gap-2">
4103
- <span [class]="selectedOption.icon"></span>
4104
- <div>{{ selectedOption.name }}</div>
4105
- </div>
4106
- </ng-template>
4107
- <ng-template #item let-languages>
4108
- <div class="flex items-center gap-2">
4109
- <span [class]="languages.icon"></span>
4110
- <div>{{ languages.name }}</div>
4111
- </div>
4112
- </ng-template>
4113
- </p-select>
4114
- </div>
4115
- <div class="flex flex-col gap-5">
4116
- <div class="mt-5">{{ 'config.dateTimeFormat' | translate }}</div>
4117
- <p-select
4118
- class="w-full md:w-56"
4119
- qa-id="oip-app-config-date-format-select"
4120
- [options]="dateFormats"
4121
- [placeholder]="'config.dateFormat' | translate"
4122
- [(ngModel)]="selectedDateFormat"
4123
- (onChange)="changeDateFormat()" />
4124
- <p-select
4125
- class="w-full md:w-56"
4126
- qa-id="oip-app-config-time-format-select"
4127
- [options]="timeFormats"
4128
- [placeholder]="'config.timeFormat' | translate"
4129
- [(ngModel)]="selectedTimeFormat"
4130
- (onChange)="changeTimeFormat()" />
4131
- <div class="mt-5">{{ 'config.timeZone' | translate }}</div>
4132
- <p-select
4133
- class="w-full md:w-56"
4134
- qa-id="oip-app-config-timezone-select"
4135
- [filter]="true"
4136
- [options]="allTimeZones"
4137
- [placeholder]="'config.timeZone' | translate"
4138
- [virtualScroll]="true"
4139
- [virtualScrollItemSize]="34"
4140
- [(ngModel)]="selectedTimeZone"
4141
- (onChange)="changeTimeZone()" />
4142
- </div>
4143
- </div>
4144
- </div>
4145
- @if (securityService.isAdmin()) {
4146
- <div class="md:w-1/2">
4147
- <div class="card flex flex-col gap-4">
4148
- <div class="font-semibold text-xl">{{ 'config.menu' | translate }}</div>
4149
- <div class="flex items-center gap-2">
4150
- <label for="oip-app-config-admin-mode">{{ 'config.all' | translate }}</label>
4151
- <p-toggle-switch
4152
- id="oip-app-config-admin-mode"
4153
- [(ngModel)]="menuService.adminMode"
4154
- (onChange)="onSwitchChange()"></p-toggle-switch>
4155
- </div>
4156
- <div class="flex items-center gap-2">
4157
- <label for="oip-app-config-admin-mode">{{ 'config.moduleManagement' | translate }}</label>
4158
- <p-button icon="pi pi-cog" label="{{ 'config.goTo' | translate }}" routerLink="/modules" />
4159
- </div>
4160
- </div>
4161
- </div>
4162
- }
4163
- </div>
5033
+ template: `
5034
+ <div class="flex flex-col md:flex-row gap-4">
5035
+ <div class="md:w-1/2">
5036
+ <div class="card flex flex-col gap-4">
5037
+ <div class="font-semibold text-xl">{{ 'config.profile' | translate }}</div>
5038
+ <div class="flex justify-content-end flex-wrap">
5039
+ {{ userService.userName }}
5040
+ </div>
5041
+ <label>
5042
+ {{ 'config.photo' | translate }}
5043
+ <span
5044
+ class="pi pi-question-circle"
5045
+ pTooltip="{{ 'config.usePhoto256x256Pixel' | translate }}"
5046
+ tooltipPosition="right"></span>
5047
+ </label>
5048
+ <div class="flex justify-content-end flex-wrap">
5049
+ <user-profile></user-profile>
5050
+ </div>
5051
+ </div>
5052
+ </div>
5053
+ <div class="md:w-1/2">
5054
+ <div class="card flex flex-col gap-4">
5055
+ <div class="font-semibold text-xl">{{ 'config.localization' | translate }}</div>
5056
+ <label> {{ 'config.selectLanguage' | translate }} </label>
5057
+ <div class="flex justify-content-end flex-wrap">
5058
+ <p-select
5059
+ class="w-full md:w-56"
5060
+ optionLabel="name"
5061
+ optionValue="code"
5062
+ qa-id="oip-app-config-language-select"
5063
+ [options]="l10nService.availableLanguages"
5064
+ [(ngModel)]="selectedLanguage"
5065
+ (onChange)="changeLanguage()">
5066
+ <ng-template #selectedItem let-selectedOption>
5067
+ <div class="flex items-center gap-2">
5068
+ <span [class]="selectedOption.icon"></span>
5069
+ <div>{{ selectedOption.name }}</div>
5070
+ </div>
5071
+ </ng-template>
5072
+ <ng-template #item let-languages>
5073
+ <div class="flex items-center gap-2">
5074
+ <span [class]="languages.icon"></span>
5075
+ <div>{{ languages.name }}</div>
5076
+ </div>
5077
+ </ng-template>
5078
+ </p-select>
5079
+ </div>
5080
+ <div class="flex flex-col gap-5">
5081
+ <div class="mt-5">{{ 'config.dateTimeFormat' | translate }}</div>
5082
+ <p-select
5083
+ class="w-full md:w-56"
5084
+ qa-id="oip-app-config-date-format-select"
5085
+ [options]="dateFormats"
5086
+ [placeholder]="'config.dateFormat' | translate"
5087
+ [(ngModel)]="selectedDateFormat"
5088
+ (onChange)="changeDateFormat()" />
5089
+ <p-select
5090
+ class="w-full md:w-56"
5091
+ qa-id="oip-app-config-time-format-select"
5092
+ [options]="timeFormats"
5093
+ [placeholder]="'config.timeFormat' | translate"
5094
+ [(ngModel)]="selectedTimeFormat"
5095
+ (onChange)="changeTimeFormat()" />
5096
+ <div class="mt-5">{{ 'config.timeZone' | translate }}</div>
5097
+ <p-select
5098
+ class="w-full md:w-56"
5099
+ qa-id="oip-app-config-timezone-select"
5100
+ [filter]="true"
5101
+ [options]="allTimeZones"
5102
+ [placeholder]="'config.timeZone' | translate"
5103
+ [virtualScroll]="true"
5104
+ [virtualScrollItemSize]="34"
5105
+ [(ngModel)]="selectedTimeZone"
5106
+ (onChange)="changeTimeZone()" />
5107
+ </div>
5108
+ </div>
5109
+ </div>
5110
+ @if (securityService.isAdmin()) {
5111
+ <div class="md:w-1/2">
5112
+ <div class="card flex flex-col gap-4">
5113
+ <div class="font-semibold text-xl">{{ 'config.menu' | translate }}</div>
5114
+ <div class="flex items-center gap-2">
5115
+ <label for="oip-app-config-admin-mode">{{ 'config.all' | translate }}</label>
5116
+ <p-toggle-switch
5117
+ id="oip-app-config-admin-mode"
5118
+ [(ngModel)]="menuService.adminMode"
5119
+ (onChange)="onSwitchChange()"></p-toggle-switch>
5120
+ </div>
5121
+ <div class="flex items-center gap-2">
5122
+ <label for="oip-app-config-admin-mode">{{ 'config.moduleManagement' | translate }}</label>
5123
+ <p-button icon="pi pi-cog" label="{{ 'config.goTo' | translate }}" routerLink="/modules" />
5124
+ </div>
5125
+ </div>
5126
+ </div>
5127
+ }
5128
+ </div>
4164
5129
  `,
4165
5130
  imports: [
4166
5131
  ProfileComponent,
@@ -4196,11 +5161,11 @@ class DbMigrationComponent extends BaseModuleComponent {
4196
5161
  });
4197
5162
  }
4198
5163
  async getData() {
4199
- return this.baseDataService.sendRequest(`api/${this.controller}/get-migrations`, 'GET');
5164
+ return this.getMigrations();
4200
5165
  }
4201
5166
  async applyMigration(rowData) {
4202
5167
  const request = { name: rowData.name };
4203
- return this.baseDataService.sendRequest(`api/${this.controller}/apply-migration`, 'POST', request);
5168
+ return this.applyModuleMigration(request);
4204
5169
  }
4205
5170
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4206
5171
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DbMigrationComponent, isStandalone: true, selector: "db-migration", providers: [ConfirmationService], usesInheritance: true, ngImport: i0, template: `
@@ -4281,7 +5246,7 @@ class DbMigrationComponent extends BaseModuleComponent {
4281
5246
  } @else if (isSecurity) {
4282
5247
  <security [controller]="controller" [id]="id"/>
4283
5248
  }
4284
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$6.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$6.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i1$6.EditableRow, selector: "[pEditableRow]", inputs: ["pEditableRow", "pEditableRowDisabled"] }, { kind: "directive", type: i1$6.CancelEditableRow, selector: "[pCancelEditableRow]" }, { kind: "component", type: i1$6.ColumnFilter, selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: SharedModule }, { kind: "ngmodule", type: TagModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5249
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$7.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$7.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i1$7.EditableRow, selector: "[pEditableRow]", inputs: ["pEditableRow", "pEditableRowDisabled"] }, { kind: "directive", type: i1$7.CancelEditableRow, selector: "[pCancelEditableRow]" }, { kind: "component", type: i1$7.ColumnFilter, selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: SharedModule }, { kind: "ngmodule", type: TagModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4285
5250
  }
4286
5251
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, decorators: [{
4287
5252
  type: Component,
@@ -4429,57 +5394,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4429
5394
 
4430
5395
  class AppModulesComponent {
4431
5396
  constructor() {
4432
- this.dataService = inject(BaseDataService);
4433
5397
  this.modules = [];
4434
5398
  this.msgService = inject(MsgService);
4435
5399
  this.confirmationService = inject(ConfirmationService);
4436
5400
  this.l10nService = inject(L10nService);
4437
- this.l10n = {};
4438
5401
  this.titleService = inject(AppTitleService);
4439
5402
  this.moduleService = inject(ModuleApi);
5403
+ this.translationsReady = firstValueFrom(this.l10nService.loadComponentTranslations('app-modules'));
4440
5404
  }
4441
5405
  async ngOnInit() {
4442
- this.l10nService.get('app-modules').subscribe((l10n) => {
4443
- this.l10n = l10n;
4444
- });
4445
- this.titleService.setTitle(this.l10n.title);
5406
+ await this.translationsReady;
5407
+ this.titleService.setTitle(this.t('app-modules.title'));
4446
5408
  await this.refreshAction();
4447
5409
  }
4448
5410
  async refreshAction() {
4449
5411
  this.modules = await this.moduleService.getModulesWithLoadStatus();
4450
5412
  }
4451
- deleteModule(module) {
5413
+ async deleteModule(module) {
5414
+ await this.translationsReady;
4452
5415
  this.confirmationService.confirm({
4453
- header: this.l10n.confirm.header,
4454
- message: this.l10n.confirm.message,
5416
+ header: this.t('app-modules.confirm.header'),
5417
+ message: this.t('app-modules.confirm.message'),
4455
5418
  icon: 'pi pi-trash',
4456
5419
  rejectButtonProps: {
4457
- label: this.l10n.confirm.cancel,
5420
+ label: this.t('app-modules.confirm.cancel'),
4458
5421
  severity: 'secondary',
4459
5422
  outlined: true
4460
5423
  },
4461
5424
  acceptButtonProps: {
4462
- label: this.l10n.confirm.delete,
5425
+ label: this.t('app-modules.confirm.delete'),
4463
5426
  severity: 'danger'
4464
5427
  },
4465
5428
  accept: async () => {
4466
- this.dataService
4467
- .sendRequest(`api/module/delete`, 'DELETE', {
4468
- moduleId: module.moduleId
4469
- })
4470
- .then(() => this.refreshAction())
4471
- .catch((error) => console.error(error));
4472
- this.msgService.success(this.l10n.messages.deleteSuccess);
5429
+ try {
5430
+ await this.moduleService.delete({
5431
+ moduleId: module.moduleId
5432
+ });
5433
+ await this.refreshAction();
5434
+ this.msgService.success(this.t('app-modules.messages.deleteSuccess'));
5435
+ }
5436
+ catch (error) {
5437
+ this.msgService.error(error);
5438
+ }
4473
5439
  }
4474
5440
  });
4475
5441
  }
5442
+ t(key) {
5443
+ return this.l10nService.instant(key);
5444
+ }
4476
5445
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppModulesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4477
5446
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: AppModulesComponent, isStandalone: true, selector: "app-modules", providers: [ConfirmationService, ModuleApi], ngImport: i0, template: `
4478
5447
  <p-confirmDialog></p-confirmDialog>
4479
5448
  <div class="flex flex-col md:flex-row gap-4">
4480
5449
  <div class="card w-full">
4481
5450
  <div class="font-semibold text-xl mb-4">
4482
- {{ l10n.title }}
5451
+ {{ 'app-modules.title' | translate }}
4483
5452
  </div>
4484
5453
  <div class="mb-4">
4485
5454
  <p-toolbar>
@@ -4528,7 +5497,7 @@ class AppModulesComponent {
4528
5497
  </p-table>
4529
5498
  </div>
4530
5499
  </div>
4531
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$6.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i4$1.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5500
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$7.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i4$2.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4532
5501
  }
4533
5502
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppModulesComponent, decorators: [{
4534
5503
  type: Component,
@@ -4541,7 +5510,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4541
5510
  <div class="flex flex-col md:flex-row gap-4">
4542
5511
  <div class="card w-full">
4543
5512
  <div class="font-semibold text-xl mb-4">
4544
- {{ l10n.title }}
5513
+ {{ 'app-modules.title' | translate }}
4545
5514
  </div>
4546
5515
  <div class="mb-4">
4547
5516
  <p-toolbar>
@@ -4592,7 +5561,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4592
5561
  </div>
4593
5562
  `
4594
5563
  }]
4595
- }] });
5564
+ }], ctorParameters: () => [] });
4596
5565
 
4597
5566
  /* eslint-disable */
4598
5567
  /* tslint:disable */
@@ -4600,17 +5569,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4600
5569
  class DiscussionApi extends HttpClient {
4601
5570
  constructor() {
4602
5571
  super(...arguments);
4603
- /**
4604
- * @description Gets comments by object type and object identifier.
4605
- *
4606
- * @tags Discussion
4607
- * @name getByObject
4608
- * @summary Gets comments by object type and object identifier.
4609
- * @request GET:/api/discussion/get-by-object
4610
- * @secure
4611
- * @response `200` `(CommentDto)[]` OK
4612
- * @response `401` `ApiExceptionResponse` Unauthorized
4613
- */
4614
5572
  this.getByObject = (query, params = {}) => this.request({
4615
5573
  path: `/api/discussion/get-by-object`,
4616
5574
  method: "GET",
@@ -4619,18 +5577,6 @@ class DiscussionApi extends HttpClient {
4619
5577
  format: "json",
4620
5578
  ...params,
4621
5579
  });
4622
- /**
4623
- * @description Gets a comment by identifier.
4624
- *
4625
- * @tags Discussion
4626
- * @name getById
4627
- * @summary Gets a comment by identifier.
4628
- * @request GET:/api/discussion/get-by-id
4629
- * @secure
4630
- * @response `200` `CommentDto` OK
4631
- * @response `401` `ApiExceptionResponse` Unauthorized
4632
- * @response `404` `ApiExceptionResponse` Not Found
4633
- */
4634
5580
  this.getById = (query, params = {}) => this.request({
4635
5581
  path: `/api/discussion/get-by-id`,
4636
5582
  method: "GET",
@@ -4639,18 +5585,6 @@ class DiscussionApi extends HttpClient {
4639
5585
  format: "json",
4640
5586
  ...params,
4641
5587
  });
4642
- /**
4643
- * @description Creates a new comment
4644
- *
4645
- * @tags Discussion
4646
- * @name create
4647
- * @summary Creates a new comment
4648
- * @request POST:/api/discussion/create
4649
- * @secure
4650
- * @response `200` `CommentDto` OK
4651
- * @response `400` `ApiExceptionResponse` Bad Request
4652
- * @response `401` `ApiExceptionResponse` Unauthorized
4653
- */
4654
5588
  this.create = (data, params = {}) => this.request({
4655
5589
  path: `/api/discussion/create`,
4656
5590
  method: "POST",
@@ -4660,20 +5594,6 @@ class DiscussionApi extends HttpClient {
4660
5594
  format: "json",
4661
5595
  ...params,
4662
5596
  });
4663
- /**
4664
- * @description Updates an existing comment.
4665
- *
4666
- * @tags Discussion
4667
- * @name update
4668
- * @summary Updates an existing comment.
4669
- * @request PUT:/api/discussion/update/{id}
4670
- * @secure
4671
- * @response `200` `CommentDto` OK
4672
- * @response `400` `ApiExceptionResponse` Bad Request
4673
- * @response `401` `ApiExceptionResponse` Unauthorized
4674
- * @response `403` `ApiExceptionResponse` Forbidden
4675
- * @response `404` `ApiExceptionResponse` Not Found
4676
- */
4677
5597
  this.update = ({ id, ...query }, data, params = {}) => this.request({
4678
5598
  path: `/api/discussion/update/${id}`,
4679
5599
  method: "PUT",
@@ -4683,37 +5603,12 @@ class DiscussionApi extends HttpClient {
4683
5603
  format: "json",
4684
5604
  ...params,
4685
5605
  });
4686
- /**
4687
- * @description Soft deletes a comment.
4688
- *
4689
- * @tags Discussion
4690
- * @name delete
4691
- * @summary Soft deletes a comment.
4692
- * @request DELETE:/api/discussion/delete/{id}
4693
- * @secure
4694
- * @response `204` `void` No Content
4695
- * @response `401` `ApiExceptionResponse` Unauthorized
4696
- * @response `403` `ApiExceptionResponse` Forbidden
4697
- * @response `404` `ApiExceptionResponse` Not Found
4698
- */
4699
5606
  this.delete = ({ id, ...query }, params = {}) => this.request({
4700
5607
  path: `/api/discussion/delete/${id}`,
4701
5608
  method: "DELETE",
4702
5609
  secure: true,
4703
5610
  ...params,
4704
5611
  });
4705
- /**
4706
- * @description Gets edit history for a comment.
4707
- *
4708
- * @tags Discussion
4709
- * @name getHistory
4710
- * @summary Gets edit history for a comment.
4711
- * @request GET:/api/discussion/get-history/{id}
4712
- * @secure
4713
- * @response `200` `(CommentHistoryDto)[]` OK
4714
- * @response `401` `ApiExceptionResponse` Unauthorized
4715
- * @response `404` `ApiExceptionResponse` Not Found
4716
- */
4717
5612
  this.getHistory = ({ id, ...query }, params = {}) => this.request({
4718
5613
  path: `/api/discussion/get-history/${id}`,
4719
5614
  method: "GET",
@@ -4721,20 +5616,6 @@ class DiscussionApi extends HttpClient {
4721
5616
  format: "json",
4722
5617
  ...params,
4723
5618
  });
4724
- /**
4725
- * @description Uploads an attachment for a comment.
4726
- *
4727
- * @tags Discussion
4728
- * @name uploadAttachment
4729
- * @summary Uploads an attachment for a comment.
4730
- * @request POST:/api/discussion/upload-attachment
4731
- * @secure
4732
- * @response `200` `AttachmentDto` OK
4733
- * @response `400` `ApiExceptionResponse` Bad Request
4734
- * @response `401` `ApiExceptionResponse` Unauthorized
4735
- * @response `403` `ApiExceptionResponse` Forbidden
4736
- * @response `404` `ApiExceptionResponse` Not Found
4737
- */
4738
5619
  this.uploadAttachment = (data, params = {}) => this.request({
4739
5620
  path: `/api/discussion/upload-attachment`,
4740
5621
  method: "POST",
@@ -4744,56 +5625,18 @@ class DiscussionApi extends HttpClient {
4744
5625
  format: "json",
4745
5626
  ...params,
4746
5627
  });
4747
- /**
4748
- * @description Deletes an attachment.
4749
- *
4750
- * @tags Discussion
4751
- * @name deleteAttachment
4752
- * @summary Deletes an attachment.
4753
- * @request DELETE:/api/discussion/delete-attachment/{id}
4754
- * @secure
4755
- * @response `204` `void` No Content
4756
- * @response `401` `ApiExceptionResponse` Unauthorized
4757
- * @response `403` `ApiExceptionResponse` Forbidden
4758
- * @response `404` `ApiExceptionResponse` Not Found
4759
- */
4760
5628
  this.deleteAttachment = ({ id, ...query }, params = {}) => this.request({
4761
5629
  path: `/api/discussion/delete-attachment/${id}`,
4762
5630
  method: "DELETE",
4763
5631
  secure: true,
4764
5632
  ...params,
4765
5633
  });
4766
- /**
4767
- * @description Downloads attachment content.
4768
- *
4769
- * @tags Discussion
4770
- * @name getAttachmentContent
4771
- * @summary Downloads attachment content.
4772
- * @request GET:/api/discussion/get-attachment-content/{id}
4773
- * @secure
4774
- * @response `200` `void` OK
4775
- * @response `401` `ApiExceptionResponse` Unauthorized
4776
- * @response `404` `ApiExceptionResponse` Not Found
4777
- */
4778
5634
  this.getAttachmentContent = ({ id, ...query }, params = {}) => this.request({
4779
5635
  path: `/api/discussion/get-attachment-content/${id}`,
4780
5636
  method: "GET",
4781
5637
  secure: true,
4782
5638
  ...params,
4783
5639
  });
4784
- /**
4785
- * @description Adds or toggles a reaction for a comment.
4786
- *
4787
- * @tags Discussion
4788
- * @name addReaction
4789
- * @summary Adds or toggles a reaction for a comment.
4790
- * @request POST:/api/discussion/add-reaction
4791
- * @secure
4792
- * @response `200` `(CommentReactionDto)[]` OK
4793
- * @response `400` `ApiExceptionResponse` Bad Request
4794
- * @response `401` `ApiExceptionResponse` Unauthorized
4795
- * @response `404` `ApiExceptionResponse` Not Found
4796
- */
4797
5640
  this.addReaction = (data, params = {}) => this.request({
4798
5641
  path: `/api/discussion/add-reaction`,
4799
5642
  method: "POST",
@@ -4803,18 +5646,6 @@ class DiscussionApi extends HttpClient {
4803
5646
  format: "json",
4804
5647
  ...params,
4805
5648
  });
4806
- /**
4807
- * @description Removes a reaction from a comment.
4808
- *
4809
- * @tags Discussion
4810
- * @name removeReaction
4811
- * @summary Removes a reaction from a comment.
4812
- * @request DELETE:/api/discussion/remove-reaction
4813
- * @secure
4814
- * @response `200` `(CommentReactionDto)[]` OK
4815
- * @response `401` `ApiExceptionResponse` Unauthorized
4816
- * @response `404` `ApiExceptionResponse` Not Found
4817
- */
4818
5649
  this.removeReaction = (query, params = {}) => this.request({
4819
5650
  path: `/api/discussion/remove-reaction`,
4820
5651
  method: "DELETE",
@@ -4823,18 +5654,6 @@ class DiscussionApi extends HttpClient {
4823
5654
  format: "json",
4824
5655
  ...params,
4825
5656
  });
4826
- /**
4827
- * @description Searches users that can be mentioned in a comment.
4828
- *
4829
- * @tags Discussion
4830
- * @name searchMentionUsers
4831
- * @summary Searches users that can be mentioned in a comment.
4832
- * @request GET:/api/discussion/search-mention-users
4833
- * @secure
4834
- * @response `200` `(MentionUserDto)[]` OK
4835
- * @response `400` `ApiExceptionResponse` Bad Request
4836
- * @response `401` `ApiExceptionResponse` Unauthorized
4837
- */
4838
5657
  this.searchMentionUsers = (query, params = {}) => this.request({
4839
5658
  path: `/api/discussion/search-mention-users`,
4840
5659
  method: "GET",
@@ -5597,7 +6416,7 @@ class DiscussionComponent {
5597
6416
  </div>
5598
6417
  }
5599
6418
  </section>
5600
- `, isInline: true, styles: [":host{display:block}.file-trigger{position:relative;display:inline-flex}.file-trigger input{position:absolute;inset:0;opacity:0;cursor:pointer}.markdown-preview,.history-markdown{word-break:break-word}.markdown-preview :is(h1,h2,h3),.history-markdown :is(h1,h2,h3){margin:0 0 .75rem;font-weight:600}.markdown-preview a,.history-markdown a{color:#2563eb;text-decoration:underline;text-underline-offset:.18em}.emoji-popover{display:flex;flex-wrap:wrap;gap:.5rem;max-width:16rem}.emoji-option{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;font-size:1.25rem;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "directive", type: i2$6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: DividerModule }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i6.Panel, selector: "p-panel", inputs: ["id", "toggleable", "header", "collapsed", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i7.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i8.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6419
+ `, isInline: true, styles: [":host{display:block}.file-trigger{position:relative;display:inline-flex}.file-trigger input{position:absolute;inset:0;opacity:0;cursor:pointer}.markdown-preview,.history-markdown{word-break:break-word}.markdown-preview :is(h1,h2,h3),.history-markdown :is(h1,h2,h3){margin:0 0 .75rem;font-weight:600}.markdown-preview a,.history-markdown a{color:#2563eb;text-decoration:underline;text-underline-offset:.18em}.emoji-popover{display:flex;flex-wrap:wrap;gap:.5rem;max-width:16rem}.emoji-option{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;font-size:1.25rem;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$2.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: DividerModule }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i6.Panel, selector: "p-panel", inputs: ["id", "toggleable", "header", "collapsed", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i7.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5601
6420
  }
5602
6421
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DiscussionComponent, decorators: [{
5603
6422
  type: Component,
@@ -6123,31 +6942,14 @@ class AuthGuardService {
6123
6942
  *
6124
6943
  * @returns {Observable<boolean | UrlTree>} A stream resolving to true (allow), or UrlTree (redirect).
6125
6944
  */
6126
- canActivate() {
6127
- return combineLatest([this.oidcSecurityService.isAuthenticated(), this.oidcSecurityService.isTokenExpired()]).pipe(switchMap(([authenticated, tokenExpired]) => {
6128
- if (!authenticated) {
6129
- return of(this.router.parseUrl('/unauthorized'));
6130
- }
6131
- if (!tokenExpired) {
6132
- return of(true);
6133
- }
6134
- // Token is expired; attempt to refresh
6135
- return this.tryRefreshToken();
6136
- }));
6945
+ canActivate(returnUrl = '/') {
6946
+ this.oidcSecurityService.auth();
6947
+ return this.oidcSecurityService.isAuthenticated().pipe(map((authenticated) => authenticated
6948
+ ? true
6949
+ : this.router.createUrlTree(['/unauthorized'], { queryParams: { returnUrl: this.getReturnUrl(returnUrl) } })));
6137
6950
  }
6138
- /**
6139
- * Attempts to refresh the session using the refresh token.
6140
- * If successful, allows route activation; otherwise, redirects to `/unauthorized`.
6141
- *
6142
- * @returns {boolean | UrlTree} A stream resolving to true or redirect UrlTree.
6143
- */
6144
- tryRefreshToken() {
6145
- return this.oidcSecurityService.forceRefreshSession().pipe(map((refreshSuccess) => {
6146
- return refreshSuccess ? true : this.router.parseUrl('/unauthorized');
6147
- }), catchError((err) => {
6148
- console.warn(err);
6149
- return of(this.router.parseUrl('/unauthorized'));
6150
- }));
6951
+ getReturnUrl(returnUrl) {
6952
+ return returnUrl.startsWith('/unauthorized') ? '/' : returnUrl;
6151
6953
  }
6152
6954
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthGuardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6153
6955
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthGuardService }); }
@@ -6156,80 +6958,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6156
6958
  type: Injectable
6157
6959
  }] });
6158
6960
 
6159
- class SecurityStorageService {
6160
- read(key) {
6161
- return localStorage.getItem(key);
6162
- }
6163
- write(key, value) {
6164
- localStorage.setItem(key, value);
6165
- }
6166
- remove(key) {
6167
- localStorage.removeItem(key);
6168
- }
6169
- clear() {
6170
- localStorage.clear();
6171
- }
6172
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6173
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityStorageService }); }
6174
- }
6175
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityStorageService, decorators: [{
6176
- type: Injectable
6177
- }] });
6178
-
6179
- class NotificationService {
6180
- constructor() {
6181
- this.securityService = inject(SecurityService);
6182
- this.msgService = inject(MsgService);
6183
- this.frontendConfig = inject(OIP_FRONTEND_CONFIG);
6184
- this.securityData = null;
6185
- this.connection = new signalR.HubConnectionBuilder()
6186
- .withUrl(this.resolveHubUrl(), {
6187
- accessTokenFactory: () => this.securityData ?? '',
6188
- skipNegotiation: true,
6189
- transport: signalR.HttpTransportType.WebSockets
6190
- })
6191
- .configureLogging(signalR.LogLevel.Error)
6192
- .build();
6193
- this.connection.on('ReceiveNotification', (notification) => {
6194
- const opt = {
6195
- severity: notification.severity,
6196
- summary: notification.subject,
6197
- detail: notification.message,
6198
- life: 0
6199
- };
6200
- this.msgService.add(opt);
6201
- });
6202
- this.securityService.getAccessToken().subscribe((token) => {
6203
- this.securityData = token;
6204
- if (!token) {
6205
- return;
6206
- }
6207
- if (this.connection.state === signalR.HubConnectionState.Disconnected) {
6208
- this.connection.start().catch((error) => console.error('Failed to start notification connection', error));
6209
- return;
6210
- }
6211
- this.connection.stop().then(() => {
6212
- this.connection.start().catch((error) => console.error('Failed to restart notification connection', error));
6213
- });
6214
- });
6215
- }
6216
- resolveHubUrl() {
6217
- if (this.frontendConfig.notificationHubUrl) {
6218
- return this.frontendConfig.notificationHubUrl;
6219
- }
6220
- if (this.frontendConfig.apiBaseUrl) {
6221
- return `${this.frontendConfig.apiBaseUrl.replace(/\/$/, '')}/hubs/notification`;
6222
- }
6223
- return '/hubs/notification';
6224
- }
6225
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6226
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, providedIn: 'root' }); }
6227
- }
6228
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, decorators: [{
6229
- type: Injectable,
6230
- args: [{ providedIn: 'root' }]
6231
- }], ctorParameters: () => [] });
6232
-
6233
6961
  /**
6234
6962
  * Prime-ng table filter service
6235
6963
  */
@@ -6304,73 +7032,96 @@ function replaceDefaults(themes) {
6304
7032
 
6305
7033
  const langIntercept = (req, next) => {
6306
7034
  const layoutService = inject(LayoutService);
7035
+ const securityService = inject(SecurityService);
6307
7036
  const lang = layoutService.language() ? layoutService.language() : 'en';
6308
- const httpHeaders = req.headers.set('Accept-language', lang);
6309
- const authReq = req.clone({
6310
- headers: httpHeaders
7037
+ const headers = req.headers
7038
+ .set('Accept-language', lang)
7039
+ .set('X-Timezone', layoutService.timeZone());
7040
+ const reqWithCredentials = req.clone({
7041
+ headers,
7042
+ withCredentials: true
6311
7043
  });
6312
- return next(authReq);
7044
+ if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method.toUpperCase())) {
7045
+ return next(reqWithCredentials);
7046
+ }
7047
+ if (req.url.includes('/api/security/create-auth-session') || req.url.includes('/api/security/get-auth-csrf-token')) {
7048
+ return next(reqWithCredentials);
7049
+ }
7050
+ return securityService.getCsrfToken().pipe(take(1), switchMap((csrfToken) => next(csrfToken?.token
7051
+ ? reqWithCredentials.clone({
7052
+ headers: reqWithCredentials.headers.set(csrfToken.headerName, csrfToken.token)
7053
+ })
7054
+ : reqWithCredentials)));
6313
7055
  };
6314
7056
 
6315
- class SecurePipe {
7057
+ const httpLoaderFactory = (http) => new TranslateHttpLoader(http);
7058
+ function provideOip() {
7059
+ return makeEnvironmentProviders([
7060
+ provideHttpClient(withInterceptors([langIntercept]), withFetch()),
7061
+ { provide: LocationStrategy, useClass: PathLocationStrategy },
7062
+ { provide: SecurityService, useClass: BffSecurityService },
7063
+ AuthGuardService,
7064
+ MessageService,
7065
+ UserService,
7066
+ NotificationService,
7067
+ UserProfileApi,
7068
+ SecurityApi,
7069
+ NotificationApi,
7070
+ importProvidersFrom([
7071
+ TranslateModule.forRoot({
7072
+ loader: {
7073
+ provide: TranslateLoader,
7074
+ useFactory: httpLoaderFactory,
7075
+ deps: [HttpClient$1]
7076
+ }
7077
+ })
7078
+ ])
7079
+ ]);
7080
+ }
7081
+
7082
+ /* eslint-disable */
7083
+ /* tslint:disable */
7084
+ // @ts-nocheck
7085
+ class FolderModuleApi extends HttpClient {
6316
7086
  constructor() {
6317
- this.http = inject(HttpClient$1);
6318
- this.sanitizer = inject(DomSanitizer);
6319
- }
6320
- transform(url) {
6321
- return this.http
6322
- .get(url, { responseType: 'blob' })
6323
- .pipe(map$1((val) => this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(val))));
7087
+ super(...arguments);
7088
+ this.getModuleInstanceSettings = (query, params = {}) => this.request({
7089
+ path: `/api/folder-module/get-module-instance-settings`,
7090
+ method: "GET",
7091
+ query: query,
7092
+ secure: true,
7093
+ format: "json",
7094
+ ...params,
7095
+ });
6324
7096
  }
6325
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
6326
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: SecurePipe, isStandalone: true, name: "secure" }); }
7097
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FolderModuleApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
7098
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FolderModuleApi }); }
6327
7099
  }
6328
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurePipe, decorators: [{
6329
- type: Pipe,
6330
- args: [{
6331
- standalone: true,
6332
- name: 'secure'
6333
- }]
7100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FolderModuleApi, decorators: [{
7101
+ type: Injectable
6334
7102
  }] });
6335
7103
 
6336
- const appendCurrentOriginSecureRoute = (secureRoutes) => {
6337
- const currentOriginRoute = `${window.location.origin}/`;
6338
- return Array.from(new Set([...(secureRoutes ?? []), currentOriginRoute]));
6339
- };
6340
- /**
6341
- * Load keycloak settings from backend and save to sessionStorage
6342
- * @param httpClient
6343
- * @returns StsConfigHttpLoader
6344
- */
6345
- const httpLoaderAuthFactory = (httpClient) => {
6346
- const KEYCLOAK_SETTINGS_KEY = 'keycloak-client-settings';
6347
- const settingsStings = sessionStorage.getItem(KEYCLOAK_SETTINGS_KEY);
6348
- if (settingsStings) {
6349
- const config$ = new Observable((subscribe) => {
6350
- subscribe.next(JSON.parse(settingsStings));
7104
+ /* eslint-disable */
7105
+ /* tslint:disable */
7106
+ // @ts-nocheck
7107
+ class IframeModuleApi extends HttpClient {
7108
+ constructor() {
7109
+ super(...arguments);
7110
+ this.getModuleInstanceSettings = (query, params = {}) => this.request({
7111
+ path: `/api/iframe-module/get-module-instance-settings`,
7112
+ method: "GET",
7113
+ query: query,
7114
+ secure: true,
7115
+ format: "json",
7116
+ ...params,
6351
7117
  });
6352
- return new StsConfigHttpLoader(config$);
6353
- }
6354
- else {
6355
- const config$ = httpClient.get(`api/security/get-keycloak-client-settings`).pipe(map$1((config) => {
6356
- const authConfig = {
6357
- authority: config.authority,
6358
- redirectUrl: window.location.origin,
6359
- postLogoutRedirectUri: window.location.origin,
6360
- clientId: config.clientId,
6361
- scope: config.scope,
6362
- responseType: config.responseType,
6363
- useRefreshToken: config.useRefreshToken,
6364
- silentRenew: config.silentRenew,
6365
- logLevel: config.logLevel,
6366
- secureRoutes: appendCurrentOriginSecureRoute(config.secureRoutes)
6367
- };
6368
- sessionStorage.setItem(KEYCLOAK_SETTINGS_KEY, JSON.stringify(authConfig));
6369
- return authConfig;
6370
- }));
6371
- return new StsConfigHttpLoader(config$);
6372
7118
  }
6373
- };
7119
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: IframeModuleApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
7120
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: IframeModuleApi }); }
7121
+ }
7122
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: IframeModuleApi, decorators: [{
7123
+ type: Injectable
7124
+ }] });
6374
7125
 
6375
7126
  // Components
6376
7127
 
@@ -6378,5 +7129,5 @@ const httpLoaderAuthFactory = (httpClient) => {
6378
7129
  * Generated bundle index. Do not edit.
6379
7130
  */
6380
7131
 
6381
- export { APP_THEME_PRESETS, APP_THEME_PRESETS_MERGE_MODE, AppConfiguratorComponent, AppFloatingConfiguratorComponent, AppLayoutComponent, AppModulesComponent, AppTopbar, AuthGuardService, BaseDataService, BaseModuleComponent, ConfigComponent, ContentType, DEFAULT_OIP_FRONTEND_CONFIG, DbMigrationComponent, DiscussionComponent, ErrorComponent, FooterComponent, HttpClient, IframeModuleComponent, KeycloakSecurityService, L10nService, LOGO_COMPONENT_TOKEN, LayoutService, LogoComponent, LogoService, MenuComponent, MenuService, MsgService, NotfoundComponent, NotificationService, OIP_FRONTEND_CONFIG, ProfileComponent, SecurePipe, SecurityComponent, SecurityDataService, SecurityService, SecurityStorageService, SidebarComponent, TableFilterService, TopBarService, UnauthorizedComponent, UserService, httpLoaderAuthFactory, langIntercept, mergeWithDefaults, provideAppThemes, provideLogoComponent, replaceDefaults };
7132
+ export { APP_THEME_PRESETS, APP_THEME_PRESETS_MERGE_MODE, AppConfiguratorComponent, AppFloatingConfiguratorComponent, AppLayoutComponent, AppModulesComponent, AppTopbar, AuthGuardService, BaseModuleComponent, BffSecurityService, ConfigComponent, ContentType, DbMigrationComponent, DiscussionComponent, ErrorComponent, FolderModuleApi, FooterComponent, HttpClient, IframeModuleApi, IframeModuleComponent, KeycloakSecurityService, L10nService, LOGO_COMPONENT_TOKEN, LayoutService, LogoComponent, LogoService, MenuComponent, MenuService, MsgService, NotfoundComponent, NotificationApi, NotificationService, ProfileComponent, SecurityApi, SecurityComponent, SecurityService, SidebarComponent, TableFilterService, TopBarService, UnauthorizedComponent, UserNotificationsComponent, UserProfileApi, UserService, convertToPrimeNgDateFormat, langIntercept, mergeWithDefaults, provideAppThemes, provideLogoComponent, provideOip, replaceDefaults };
6382
7133
  //# sourceMappingURL=oip-common.mjs.map