oip-common 0.1.7 → 0.2.0

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,5 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, InjectionToken, signal, computed, effect, ChangeDetectorRef, Component, Input, PLATFORM_ID, HostBinding, EventEmitter, Output, ViewChild, Renderer2, SecurityContext, ChangeDetectionStrategy, makeEnvironmentProviders, Pipe } 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';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
4
  import * as i2$6 from 'primeng/api';
4
5
  import { MessageService, ConfirmationService, PrimeIcons, SharedModule } from 'primeng/api';
5
6
  import { HttpErrorResponse, HttpClient as HttpClient$1, HttpHeaders } from '@angular/common/http';
@@ -7,8 +8,11 @@ import * as i2$4 from '@ngx-translate/core';
7
8
  import { TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
8
9
  import * as i1$3 from '@angular/router';
9
10
  import { ActivatedRoute, Router, RouterModule, NavigationEnd, RouterLinkActive, RouterLink } from '@angular/router';
10
- import { lastValueFrom, BehaviorSubject, Subject, ReplaySubject, merge, filter as filter$1, combineLatest, of, map as map$1, Observable } from 'rxjs';
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
13
  import { Title, DomSanitizer } from '@angular/platform-browser';
14
+ import { PrimeNG } from 'primeng/config';
15
+ import { OidcSecurityService, PublicEventsService, EventTypes, StsConfigHttpLoader } from 'angular-auth-oidc-client';
12
16
  import * as i1 from 'primeng/multiselect';
13
17
  import { MultiSelectModule } from 'primeng/multiselect';
14
18
  import * as i2 from 'primeng/tooltip';
@@ -25,11 +29,8 @@ import { updatePreset, updateSurfacePalette, $t } from '@primeng/themes';
25
29
  import Aura from '@primeng/themes/aura';
26
30
  import Lara from '@primeng/themes/lara';
27
31
  import Nora from '@primeng/themes/nora';
28
- import { PrimeNG } from 'primeng/config';
29
32
  import * as i3 from 'primeng/selectbutton';
30
33
  import { SelectButtonModule } from 'primeng/selectbutton';
31
- import { OidcSecurityService, PublicEventsService, EventTypes, StsConfigHttpLoader } from 'angular-auth-oidc-client';
32
- import { filter, distinctUntilChanged, map, switchMap, catchError } from 'rxjs/operators';
33
34
  import { Tabs, TabList, Tab } from 'primeng/tabs';
34
35
  import * as i2$2 from 'primeng/avatar';
35
36
  import { AvatarModule } from 'primeng/avatar';
@@ -38,7 +39,7 @@ import { ContextMenuModule, ContextMenu } from 'primeng/contextmenu';
38
39
  import * as i2$3 from 'primeng/dialog';
39
40
  import { DialogModule } from 'primeng/dialog';
40
41
  import * as i3$3 from 'primeng/inputtext';
41
- import { InputTextModule } from 'primeng/inputtext';
42
+ import { InputTextModule, InputText } from 'primeng/inputtext';
42
43
  import { trigger, state, transition, style, animate } from '@angular/animations';
43
44
  import * as i3$2 from 'primeng/ripple';
44
45
  import { RippleModule } from 'primeng/ripple';
@@ -144,7 +145,11 @@ class MsgService {
144
145
  });
145
146
  }
146
147
  error(detail, summary = this.translate.instant('msgService.error'), life = this.lifetime) {
147
- if (detail instanceof HttpErrorResponse) {
148
+ const validationMessage = this.extractValidationMessage(detail);
149
+ if (validationMessage) {
150
+ detail = validationMessage;
151
+ }
152
+ else if (detail instanceof HttpErrorResponse) {
148
153
  summary = `Error: ${detail.status} ${detail.statusText}`;
149
154
  detail = `${detail.name} \r\n ${detail.message}`;
150
155
  }
@@ -156,6 +161,10 @@ class MsgService {
156
161
  });
157
162
  }
158
163
  extractErrorMessage(error, fallback) {
164
+ const validationMessage = this.extractValidationMessage(error);
165
+ if (validationMessage) {
166
+ return validationMessage;
167
+ }
159
168
  if (typeof error === 'object' &&
160
169
  error &&
161
170
  'error' in error &&
@@ -170,6 +179,35 @@ class MsgService {
170
179
  }
171
180
  return fallback;
172
181
  }
182
+ extractValidationMessage(error) {
183
+ const responseError = this.getObjectProperty(error, 'error');
184
+ const validationErrors = this.getObjectProperty(responseError, 'errors') ?? this.getObjectProperty(error, 'errors');
185
+ if (!validationErrors) {
186
+ return null;
187
+ }
188
+ const messages = Object.entries(validationErrors)
189
+ .reduce((result, [field, value]) => [...result, ...this.toValidationMessages(field, value)], [])
190
+ .filter((message) => message.length > 0);
191
+ return messages.length > 0 ? messages.join('\n') : null;
192
+ }
193
+ toValidationMessages(field, value) {
194
+ if (Array.isArray(value)) {
195
+ return value
196
+ .filter((message) => typeof message === 'string')
197
+ .map((message) => `${field}: ${message}`);
198
+ }
199
+ if (typeof value === 'string') {
200
+ return [`${field}: ${value}`];
201
+ }
202
+ return [];
203
+ }
204
+ getObjectProperty(source, property) {
205
+ if (typeof source !== 'object' || source === null || !(property in source)) {
206
+ return null;
207
+ }
208
+ const value = source[property];
209
+ return typeof value === 'object' && value !== null ? value : null;
210
+ }
173
211
  errorFromException(error, fallback, summary = fallback, life = this.lifetime) {
174
212
  this.error(this.extractErrorMessage(error, fallback), summary, life);
175
213
  }
@@ -567,7 +605,219 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
567
605
  args: [{ providedIn: 'root' }]
568
606
  }], ctorParameters: () => [] });
569
607
 
608
+ /**
609
+ * Service for managing translation loading in the application
610
+ */
611
+ class L10nService {
612
+ constructor() {
613
+ this.loadedTranslations = new Set();
614
+ this.httpClient = inject(HttpClient$1);
615
+ this.translateService = inject(TranslateService);
616
+ this.primeNg = inject(PrimeNG);
617
+ this.layoutService = inject(LayoutService);
618
+ }
619
+ /**
620
+ * Loads translations for a specific component
621
+ * @param component - Name of the component to load translations for
622
+ */
623
+ loadComponentTranslations(component) {
624
+ const lang = this.translateService.currentLang;
625
+ this.loadTranslations(component, lang);
626
+ }
627
+ /**
628
+ * Gets the translated value of a key (or an array of keys)
629
+ * @returns the translated key, or an object of translated keys
630
+ */
631
+ get(key) {
632
+ this.loadComponentTranslations(key.split('.')[0]);
633
+ return this.translateService.get(key);
634
+ }
635
+ /**
636
+ * Internal method to load translations from JSON files
637
+ * @param component - Component or translation namespace
638
+ * @param lang - Language code to load translations for
639
+ */
640
+ loadTranslations(component, lang) {
641
+ const key = `${component}.${lang}`;
642
+ 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
+ });
651
+ }
652
+ catch (e) {
653
+ console.error(`No translations found for ${component}.${lang}.json`);
654
+ console.error(e);
655
+ }
656
+ }
657
+ /**
658
+ * Changes the lang currently used
659
+ */
660
+ use(selectedLanguage, key = null) {
661
+ if (key) {
662
+ this.get(key);
663
+ }
664
+ this.translateService.use(selectedLanguage);
665
+ }
666
+ init(languages) {
667
+ this.availableLanguages = languages;
668
+ this.translateService.addLangs(languages.map((x) => x.code));
669
+ const lang = this.layoutService.language() ? this.layoutService.language() : 'en';
670
+ this.translateService.setDefaultLang(lang);
671
+ this.translateService.use(lang).subscribe(() => {
672
+ this.loadComponentTranslations('app-info');
673
+ this.translateService.get('primeng').subscribe((res) => this.primeNg.setTranslation(res));
674
+ });
675
+ }
676
+ instant(key, interpolateParams) {
677
+ return this.translateService.instant(key, interpolateParams);
678
+ }
679
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
680
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, providedIn: 'root' }); }
681
+ }
682
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, decorators: [{
683
+ type: Injectable,
684
+ args: [{ providedIn: 'root' }]
685
+ }] });
686
+
687
+ class SecurityDataService extends BaseDataService {
688
+ getSecurity(controller, id) {
689
+ return this.sendRequest(this.baseUrl + `api/${controller}/get-security?id=${id}`);
690
+ }
691
+ saveSecurity(controller, request) {
692
+ return this.sendRequest(this.baseUrl + `api/${controller}/put-security`, 'PUT', request);
693
+ }
694
+ getRealmRoles() {
695
+ return this.sendRequest(this.baseUrl + `api/security/get-realm-roles`);
696
+ }
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 }); }
699
+ }
700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService, decorators: [{
701
+ type: Injectable
702
+ }] });
703
+
704
+ class SecurityService {
705
+ }
706
+ /**
707
+ * SecurityService extends OidcSecurityService to manage authentication,
708
+ * token handling, and user role access in an Angular application.
709
+ *
710
+ * It provides helper methods for checking authentication, managing tokens,
711
+ * determining user roles, and performing logout and refresh operations.
712
+ */
713
+ class KeycloakSecurityService extends OidcSecurityService {
714
+ /**
715
+ * Initializes service and subscribes to authentication events.
716
+ * When a 'NewAuthenticationResult' event is received, the `auth` method is called.
717
+ */
718
+ constructor() {
719
+ super();
720
+ /**
721
+ * Handles angular OIDC events.
722
+ */
723
+ this.publicEventsService = inject(PublicEventsService);
724
+ /**
725
+ * Stores the latest login response from checkAuth().
726
+ */
727
+ this.loginResponse = new BehaviorSubject(null);
728
+ /**
729
+ * Stores the decoded access token payload.
730
+ */
731
+ this.payload = new BehaviorSubject(null);
732
+ /**
733
+ * Stores user-specific data from the login response.
734
+ */
735
+ this.currentUser = new BehaviorSubject(null);
736
+ /**
737
+ * Emits access token updates from initial auth check, manual refresh,
738
+ * and library authentication events.
739
+ */
740
+ this.accessToken = new ReplaySubject(1);
741
+ this.publicEventsService
742
+ .registerForEvents()
743
+ .pipe(filter((event) => event.type === EventTypes.NewAuthenticationResult))
744
+ .subscribe(() => {
745
+ super.getAccessToken().subscribe(token => { this.accessToken.next(token); });
746
+ this.auth();
747
+ });
748
+ }
749
+ getCurrentUser() {
750
+ return this.currentUser.getValue();
751
+ }
752
+ getCurrentUser$() {
753
+ return this.currentUser.asObservable();
754
+ }
755
+ /**
756
+ * Returns the ID token for the sign-in.
757
+ * @returns A string with the id token.
758
+ */
759
+ getAccessToken(configId) {
760
+ return merge(super.getAccessToken(configId), this.accessToken.asObservable()).pipe(distinctUntilChanged());
761
+ }
762
+ /**
763
+ * Indicates whether the current user has the 'admin' role.
764
+ *
765
+ * @returns {boolean} True if the user is an admin, false otherwise.
766
+ */
767
+ isAdmin() {
768
+ return this.payload.getValue()?.realm_access?.roles?.includes('admin');
769
+ }
770
+ /**
771
+ * Initiates authentication check and updates login response, user data,
772
+ * and decoded token payload if authenticated.
773
+ */
774
+ auth() {
775
+ super.checkAuth().subscribe((_response) => {
776
+ this.loginResponse.next(_response);
777
+ this.currentUser.next(_response.userData);
778
+ this.getPayloadFromAccessToken().subscribe((_token) => {
779
+ this.payload.next(_token);
780
+ });
781
+ });
782
+ }
783
+ /**
784
+ * Performs logout and clears the local token payload.
785
+ *
786
+ * @param {string} [configId] Optional configuration ID for logout.
787
+ * @param {LogoutAuthOptions} [logoutAuthOptions] Optional logout options.
788
+ */
789
+ logout(configId, logoutAuthOptions) {
790
+ this.logoff(configId, logoutAuthOptions).subscribe((x) => this.payload.next(x));
791
+ }
792
+ /**
793
+ * Completes the BehaviorSubjects when the service is destroyed to avoid memory leaks.
794
+ */
795
+ ngOnDestroy() {
796
+ this.loginResponse.complete();
797
+ this.payload.complete();
798
+ this.currentUser.complete();
799
+ }
800
+ /**
801
+ * Checks whether the current access token is expired based on the 'exp' claim.
802
+ *
803
+ * @returns {Observable<boolean>} Observable that emits true if the token is expired.
804
+ */
805
+ isTokenExpired() {
806
+ return this.getPayloadFromAccessToken().pipe(map((payload) => {
807
+ return payload.exp < Math.floor(Date.now() / 1000);
808
+ }));
809
+ }
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
+
570
817
  class BaseModuleComponent {
818
+ static { this.readRight = 'read'; }
819
+ static { this.editRight = 'edit'; }
820
+ static { this.deleteRight = 'delete'; }
571
821
  /**
572
822
  * Updates local settings and persists them to local storage.
573
823
  * @return {void}
@@ -615,12 +865,21 @@ class BaseModuleComponent {
615
865
  get isSecurity() {
616
866
  return this.topBarService.checkId('security');
617
867
  }
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);
873
+ }
618
874
  /**
619
875
  * Initializes the component and subscribes to local settings updates.
620
876
  */
621
877
  constructor() {
622
878
  this.isInitialized = false;
623
879
  this.moduleInstanceReloadPromise = Promise.resolve();
880
+ this.destroyRef = inject(DestroyRef);
881
+ this.securityDataService = inject(SecurityDataService);
882
+ this.securityService = inject(SecurityService);
624
883
  /**
625
884
  * Provide access to app settings
626
885
  */
@@ -683,6 +942,11 @@ class BaseModuleComponent {
683
942
  * @type {Subject<TLocalStoreSettings>}
684
943
  */
685
944
  this.localSettingsUpdate = new Subject();
945
+ this.l10nService = inject(L10nService);
946
+ this.canRead = false;
947
+ this.canEdit = false;
948
+ this.canDelete = false;
949
+ this.securityRightsLoaded = false;
686
950
  /**
687
951
  * A unique numerical identifier for the module.
688
952
  * @type {number}
@@ -704,6 +968,7 @@ class BaseModuleComponent {
704
968
  });
705
969
  this.subscriptions.push(this.route.url.subscribe((url) => {
706
970
  this.controller = url[0].path;
971
+ this.l10n$ = this.l10nService.get(this.controller);
707
972
  }));
708
973
  this.subscriptions.push(this.route.paramMap.subscribe((params) => {
709
974
  const routeId = params.get('id');
@@ -725,6 +990,7 @@ class BaseModuleComponent {
725
990
  ngOnDestroy() {
726
991
  this.topBarService.setTopBarItems([]);
727
992
  this.topBarService.activeId = this.topBarItems[0].id;
993
+ this.rightsSubscription?.unsubscribe();
728
994
  this.subscriptions.forEach((s) => s.unsubscribe());
729
995
  }
730
996
  /**
@@ -782,10 +1048,58 @@ class BaseModuleComponent {
782
1048
  * Called whenever the module instance changes, including the first load.
783
1049
  * Derived components can override this to refresh module-specific data.
784
1050
  */
785
- async onModuleInstanceChange() { }
1051
+ async onModuleInstanceChange() {
1052
+ }
1053
+ /**
1054
+ * Called whenever current user rights for the active module instance are recalculated.
1055
+ */
1056
+ onSecurityRightsChange() {
1057
+ }
1058
+ /**
1059
+ * Starts watching current token roles and maps them to module instance security settings.
1060
+ */
1061
+ watchSecurityRights(controller = this.controller, id = this.id) {
1062
+ this.rightsSubscription?.unsubscribe();
1063
+ this.resetRightsState();
1064
+ if (!controller || id == null) {
1065
+ return;
1066
+ }
1067
+ this.rightsSubscription = this.securityService.payload
1068
+ .pipe(switchMap((payload) => from(this.securityDataService.getSecurity(controller, id)).pipe(map((securitySettings) => ({ payload, securitySettings })))), takeUntilDestroyed(this.destroyRef))
1069
+ .subscribe({
1070
+ next: ({ payload, securitySettings }) => {
1071
+ const roles = payload?.realm_access?.roles ?? [];
1072
+ this.updateRightsState(roles, securitySettings);
1073
+ },
1074
+ error: (error) => {
1075
+ this.securityRightsLoaded = true;
1076
+ console.error('Не удалось загрузить права', error);
1077
+ this.onSecurityRightsChange();
1078
+ }
1079
+ });
1080
+ }
1081
+ resetRightsState() {
1082
+ this.canRead = false;
1083
+ this.canEdit = false;
1084
+ this.canDelete = false;
1085
+ this.securityRightsLoaded = false;
1086
+ }
1087
+ updateRightsState(roles, securitySettings) {
1088
+ this.canRead = this.hasSecurityRight(roles, securitySettings, BaseModuleComponent.readRight);
1089
+ this.canEdit = this.hasSecurityRight(roles, securitySettings, BaseModuleComponent.editRight);
1090
+ this.canDelete = this.hasSecurityRight(roles, securitySettings, BaseModuleComponent.deleteRight);
1091
+ this.securityRightsLoaded = true;
1092
+ this.onSecurityRightsChange();
1093
+ }
1094
+ hasSecurityRight(roles, securitySettings, code) {
1095
+ return securitySettings
1096
+ .find((security) => security.code === code)
1097
+ ?.roles?.some((role) => roles.includes(role)) ?? false;
1098
+ }
786
1099
  async reloadModuleInstance() {
787
1100
  this.moduleInstanceReloadPromise = this.moduleInstanceReloadPromise.then(async () => {
788
1101
  await this.getSettings();
1102
+ this.watchSecurityRights();
789
1103
  await this.onModuleInstanceChange();
790
1104
  });
791
1105
  await this.moduleInstanceReloadPromise;
@@ -798,48 +1112,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
798
1112
  args: [{ standalone: true, template: '' }]
799
1113
  }], ctorParameters: () => [] });
800
1114
 
801
- class SecurityDataService extends BaseDataService {
802
- getSecurity(controller, id) {
803
- return this.sendRequest(this.baseUrl + `api/${controller}/get-security?id=${id}`);
804
- }
805
- saveSecurity(controller, request) {
806
- return this.sendRequest(this.baseUrl + `api/${controller}/put-security`, 'PUT', request);
807
- }
808
- getRealmRoles() {
809
- return this.sendRequest(this.baseUrl + `api/security/get-realm-roles`);
810
- }
811
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
812
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService }); }
813
- }
814
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityDataService, decorators: [{
815
- type: Injectable
816
- }] });
817
-
818
1115
  class SecurityComponent {
819
1116
  constructor() {
820
1117
  this.msgService = inject(MsgService);
821
1118
  this.dataService = inject(SecurityDataService);
822
1119
  this.translateService = inject(TranslateService);
1120
+ this.securityLoadToken = 0;
1121
+ this.securityData = [];
823
1122
  this.roles = [];
824
1123
  }
825
1124
  ngOnDestroy() {
826
1125
  // on destroy
827
1126
  }
828
- ngOnInit() {
829
- if (!this.id) {
830
- this.msgService.error('Module id not passed!');
831
- }
832
- if (!this.controller) {
833
- this.msgService.error('Controller not passed!');
1127
+ ngOnChanges(changes) {
1128
+ if (changes['id'] || changes['controller']) {
1129
+ void this.loadSecurity();
834
1130
  }
835
- this.dataService.getSecurity(this.controller, this.id).then((result) => {
836
- this.securityData = result;
837
- }, (error) => this.msgService.error(error));
1131
+ }
1132
+ ngOnInit() {
838
1133
  this.dataService.getRealmRoles().then((result) => {
839
1134
  this.roles = result;
840
1135
  }, (error) => this.msgService.error(error));
841
1136
  }
842
1137
  saveClick() {
1138
+ if (this.id == null) {
1139
+ this.msgService.error('Module id not passed!');
1140
+ return;
1141
+ }
1142
+ if (!this.controller) {
1143
+ this.msgService.error('Controller not passed!');
1144
+ return;
1145
+ }
843
1146
  const request = {
844
1147
  id: this.id,
845
1148
  securities: this.securityData
@@ -850,80 +1153,100 @@ class SecurityComponent {
850
1153
  }
851
1154
  saveKeyDown($event) {
852
1155
  if ($event.key === 'Enter' || $event.key === 'Space') {
853
- this.saveKeyDown(null);
1156
+ this.saveClick();
1157
+ }
1158
+ }
1159
+ async loadSecurity() {
1160
+ const loadToken = ++this.securityLoadToken;
1161
+ const controller = this.controller;
1162
+ const id = this.id;
1163
+ this.securityData = [];
1164
+ if (!controller || id == null) {
1165
+ return;
1166
+ }
1167
+ try {
1168
+ const result = await this.dataService.getSecurity(controller, id);
1169
+ if (loadToken === this.securityLoadToken) {
1170
+ this.securityData = result;
1171
+ }
1172
+ }
1173
+ catch (error) {
1174
+ if (loadToken === this.securityLoadToken) {
1175
+ this.msgService.error(error);
1176
+ }
854
1177
  }
855
1178
  }
856
1179
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
857
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: SecurityComponent, isStandalone: true, selector: "security", inputs: { id: "id", controller: "controller" }, ngImport: i0, template: `
858
- <div class="flex flex-col md:flex-row gap-8">
859
- <div class="md:w-1/2">
860
- <div class="card flex flex-col gap-4">
861
- <div class="font-semibold text-xl">
862
- {{ 'securityComponent.security' | translate }}
863
- </div>
864
- @for (item of securityData; track item.name) {
865
- <div class="flex flex-col gap-2">
866
- <label htmlFor="oip-security-multiselect-{{ item.name }}">
867
- {{ item.name }}
868
- <span class="pi pi-question-circle" pTooltip="{{ item.description }}" tooltipPosition="right"></span>
869
- </label>
870
- <p-multiSelect
871
- id="oip-security-multiselect-{{ item.name }}"
872
- placeholder="Select roles"
873
- [maxSelectedLabels]="10"
874
- [options]="roles"
875
- [(ngModel)]="item.roles" />
876
- </div>
877
- }
878
- <div class="flex justify-content-end flex-wrap">
879
- <p-button
880
- icon="pi pi-save"
881
- id="oip-security-save-button"
882
- label="{{ 'securityComponent.save' | translate }}"
883
- (click)="saveClick()"
884
- (keydown)="saveKeyDown($event)" />
885
- </div>
886
- </div>
887
- </div>
888
- </div>
1180
+ 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
+ <div class="flex flex-col md:flex-row gap-8">
1182
+ <div class="md:w-1/2">
1183
+ <div class="card flex flex-col gap-4">
1184
+ <div class="font-semibold text-xl">
1185
+ {{ 'securityComponent.security' | translate }}
1186
+ </div>
1187
+ @for (item of securityData; track item.name) {
1188
+ <div class="flex flex-col gap-2">
1189
+ <label htmlFor="oip-security-multiselect-{{ item.name }}">
1190
+ {{ item.name }}
1191
+ <span class="pi pi-question-circle" pTooltip="{{ item.description }}" tooltipPosition="right"></span>
1192
+ </label>
1193
+ <p-multiSelect
1194
+ id="oip-security-multiselect-{{ item.name }}"
1195
+ placeholder="{{ 'securityComponent.selectRoles' | translate }}"
1196
+ [maxSelectedLabels]="10"
1197
+ [options]="roles"
1198
+ [(ngModel)]="item.roles" />
1199
+ </div>
1200
+ }
1201
+ <div class="flex justify-content-end flex-wrap">
1202
+ <p-button
1203
+ icon="pi pi-save"
1204
+ id="oip-security-save-button"
1205
+ label="{{ 'securityComponent.save' | translate }}"
1206
+ (click)="saveClick()"
1207
+ (keydown)="saveKeyDown($event)" />
1208
+ </div>
1209
+ </div>
1210
+ </div>
1211
+ </div>
889
1212
  `, isInline: true, dependencies: [{ 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: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.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: "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" }] }); }
890
1213
  }
891
1214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SecurityComponent, decorators: [{
892
1215
  type: Component,
893
1216
  args: [{
894
1217
  selector: 'security',
895
- template: `
896
- <div class="flex flex-col md:flex-row gap-8">
897
- <div class="md:w-1/2">
898
- <div class="card flex flex-col gap-4">
899
- <div class="font-semibold text-xl">
900
- {{ 'securityComponent.security' | translate }}
901
- </div>
902
- @for (item of securityData; track item.name) {
903
- <div class="flex flex-col gap-2">
904
- <label htmlFor="oip-security-multiselect-{{ item.name }}">
905
- {{ item.name }}
906
- <span class="pi pi-question-circle" pTooltip="{{ item.description }}" tooltipPosition="right"></span>
907
- </label>
908
- <p-multiSelect
909
- id="oip-security-multiselect-{{ item.name }}"
910
- placeholder="Select roles"
911
- [maxSelectedLabels]="10"
912
- [options]="roles"
913
- [(ngModel)]="item.roles" />
914
- </div>
915
- }
916
- <div class="flex justify-content-end flex-wrap">
917
- <p-button
918
- icon="pi pi-save"
919
- id="oip-security-save-button"
920
- label="{{ 'securityComponent.save' | translate }}"
921
- (click)="saveClick()"
922
- (keydown)="saveKeyDown($event)" />
923
- </div>
924
- </div>
925
- </div>
926
- </div>
1218
+ template: `
1219
+ <div class="flex flex-col md:flex-row gap-8">
1220
+ <div class="md:w-1/2">
1221
+ <div class="card flex flex-col gap-4">
1222
+ <div class="font-semibold text-xl">
1223
+ {{ 'securityComponent.security' | translate }}
1224
+ </div>
1225
+ @for (item of securityData; track item.name) {
1226
+ <div class="flex flex-col gap-2">
1227
+ <label htmlFor="oip-security-multiselect-{{ item.name }}">
1228
+ {{ item.name }}
1229
+ <span class="pi pi-question-circle" pTooltip="{{ item.description }}" tooltipPosition="right"></span>
1230
+ </label>
1231
+ <p-multiSelect
1232
+ id="oip-security-multiselect-{{ item.name }}"
1233
+ placeholder="{{ 'securityComponent.selectRoles' | translate }}"
1234
+ [maxSelectedLabels]="10"
1235
+ [options]="roles"
1236
+ [(ngModel)]="item.roles" />
1237
+ </div>
1238
+ }
1239
+ <div class="flex justify-content-end flex-wrap">
1240
+ <p-button
1241
+ icon="pi pi-save"
1242
+ id="oip-security-save-button"
1243
+ label="{{ 'securityComponent.save' | translate }}"
1244
+ (click)="saveClick()"
1245
+ (keydown)="saveKeyDown($event)" />
1246
+ </div>
1247
+ </div>
1248
+ </div>
1249
+ </div>
927
1250
  `,
928
1251
  imports: [MultiSelectModule, TooltipModule, FormsModule, ButtonModule, TranslatePipe],
929
1252
  standalone: true
@@ -1447,195 +1770,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1447
1770
  args: [{
1448
1771
  selector: 'app-configurator',
1449
1772
  standalone: true,
1450
- imports: [CommonModule, FormsModule, SelectButtonModule, TranslatePipe],
1451
- template: `
1452
- <div class="flex flex-col gap-4">
1453
- <div>
1454
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.primary' | translate }}</span>
1455
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1456
- @for (primaryColor of primaryColors(); track primaryColor.name) {
1457
- <button
1458
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1459
- id="oip-app-configurator-primary-color-{{ primaryColor.name }}"
1460
- type="button"
1461
- [ngClass]="{
1462
- 'outline-primary': primaryColor.name === selectedPrimaryColor()
1463
- }"
1464
- [style]="{
1465
- 'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
1466
- }"
1467
- [title]="primaryColor.name"
1468
- (click)="updateColors($event, 'primary', primaryColor)"></button>
1469
- }
1470
- </div>
1471
- </div>
1472
- <div>
1473
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.surface' | translate }}</span>
1474
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1475
- @for (surface of surfaceColors(); track surface.name) {
1476
- <button
1477
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1478
- id="oip-app-configurator-surface-color-{{ surface.name }}"
1479
- type="button"
1480
- [ngClass]="{
1481
- 'outline-primary': selectedSurfaceColor()
1482
- ? selectedSurfaceColor() === surface.name
1483
- : layoutService.layoutConfig().darkTheme
1484
- ? surface.name === 'zinc'
1485
- : surface.name === 'slate'
1486
- }"
1487
- [style]="{
1488
- 'background-color': surface?.name === 'noir' ? 'var(--text-color)' : surface?.palette?.['500']
1489
- }"
1490
- [title]="surface.name"
1491
- (click)="updateColors($event, 'surface', surface)"></button>
1492
- }
1493
- </div>
1494
- </div>
1495
- <div class="flex flex-col gap-2">
1496
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1497
- <p-selectButton
1498
- id="oip-app-configurator-preset-select-button"
1499
- size="small"
1500
- [allowEmpty]="false"
1501
- [ngModel]="selectedPreset()"
1502
- [options]="presets"
1503
- optionLabel="label"
1504
- optionValue="value"
1505
- (ngModelChange)="onPresetChange($event)" />
1506
- </div>
1507
- @if (showMenuModeButton()) {
1508
- <div class="flex flex-col gap-2">
1509
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.menuMode' | translate }}</span>
1510
- <p-selectButton
1511
- id="oip-app-configurator-menu-mode-select-button"
1512
- size="small"
1513
- [allowEmpty]="false"
1514
- [ngModel]="menuMode()"
1515
- [options]="menuModeOptions"
1516
- (ngModelChange)="onMenuModeChange($event)" />
1517
- </div>
1518
- }
1519
- </div>
1520
- `,
1521
- host: {
1522
- class: 'hidden absolute top-[3.25rem] right-0 w-72 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]'
1523
- }
1524
- }]
1525
- }] });
1526
-
1527
- class SecurityService {
1528
- }
1529
- /**
1530
- * SecurityService extends OidcSecurityService to manage authentication,
1531
- * token handling, and user role access in an Angular application.
1532
- *
1533
- * It provides helper methods for checking authentication, managing tokens,
1534
- * determining user roles, and performing logout and refresh operations.
1535
- */
1536
- class KeycloakSecurityService extends OidcSecurityService {
1537
- /**
1538
- * Initializes service and subscribes to authentication events.
1539
- * When a 'NewAuthenticationResult' event is received, the `auth` method is called.
1540
- */
1541
- constructor() {
1542
- super();
1543
- /**
1544
- * Handles angular OIDC events.
1545
- */
1546
- this.publicEventsService = inject(PublicEventsService);
1547
- /**
1548
- * Stores the latest login response from checkAuth().
1549
- */
1550
- this.loginResponse = new BehaviorSubject(null);
1551
- /**
1552
- * Stores the decoded access token payload.
1553
- */
1554
- this.payload = new BehaviorSubject(null);
1555
- /**
1556
- * Stores user-specific data from the login response.
1557
- */
1558
- this.currentUser = new BehaviorSubject(null);
1559
- /**
1560
- * Emits access token updates from initial auth check, manual refresh,
1561
- * and library authentication events.
1562
- */
1563
- this.accessToken = new ReplaySubject(1);
1564
- this.publicEventsService
1565
- .registerForEvents()
1566
- .pipe(filter((event) => event.type === EventTypes.NewAuthenticationResult))
1567
- .subscribe(() => {
1568
- super.getAccessToken().subscribe(token => { this.accessToken.next(token); });
1569
- this.auth();
1570
- });
1571
- }
1572
- getCurrentUser() {
1573
- return this.currentUser.getValue();
1574
- }
1575
- getCurrentUser$() {
1576
- return this.currentUser.asObservable();
1577
- }
1578
- /**
1579
- * Returns the ID token for the sign-in.
1580
- * @returns A string with the id token.
1581
- */
1582
- getAccessToken(configId) {
1583
- return merge(super.getAccessToken(configId), this.accessToken.asObservable()).pipe(distinctUntilChanged());
1584
- }
1585
- /**
1586
- * Indicates whether the current user has the 'admin' role.
1587
- *
1588
- * @returns {boolean} True if the user is an admin, false otherwise.
1589
- */
1590
- isAdmin() {
1591
- return this.payload.getValue()?.realm_access?.roles?.includes('admin');
1592
- }
1593
- /**
1594
- * Initiates authentication check and updates login response, user data,
1595
- * and decoded token payload if authenticated.
1596
- */
1597
- auth() {
1598
- super.checkAuth().subscribe((_response) => {
1599
- this.loginResponse.next(_response);
1600
- this.currentUser.next(_response.userData);
1601
- this.getPayloadFromAccessToken().subscribe((_token) => {
1602
- this.payload.next(_token);
1603
- });
1604
- });
1605
- }
1606
- /**
1607
- * Performs logout and clears the local token payload.
1608
- *
1609
- * @param {string} [configId] Optional configuration ID for logout.
1610
- * @param {LogoutAuthOptions} [logoutAuthOptions] Optional logout options.
1611
- */
1612
- logout(configId, logoutAuthOptions) {
1613
- this.logoff(configId, logoutAuthOptions).subscribe((x) => this.payload.next(x));
1614
- }
1615
- /**
1616
- * Completes the BehaviorSubjects when the service is destroyed to avoid memory leaks.
1617
- */
1618
- ngOnDestroy() {
1619
- this.loginResponse.complete();
1620
- this.payload.complete();
1621
- this.currentUser.complete();
1622
- }
1623
- /**
1624
- * Checks whether the current access token is expired based on the 'exp' claim.
1625
- *
1626
- * @returns {Observable<boolean>} Observable that emits true if the token is expired.
1627
- */
1628
- isTokenExpired() {
1629
- return this.getPayloadFromAccessToken().pipe(map((payload) => {
1630
- return payload.exp < Math.floor(Date.now() / 1000);
1631
- }));
1632
- }
1633
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1634
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService }); }
1635
- }
1636
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, decorators: [{
1637
- type: Injectable
1638
- }], ctorParameters: () => [] });
1773
+ imports: [CommonModule, FormsModule, SelectButtonModule, TranslatePipe],
1774
+ template: `
1775
+ <div class="flex flex-col gap-4">
1776
+ <div>
1777
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.primary' | translate }}</span>
1778
+ <div class="pt-2 flex gap-2 flex-wrap justify-start">
1779
+ @for (primaryColor of primaryColors(); track primaryColor.name) {
1780
+ <button
1781
+ class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1782
+ id="oip-app-configurator-primary-color-{{ primaryColor.name }}"
1783
+ type="button"
1784
+ [ngClass]="{
1785
+ 'outline-primary': primaryColor.name === selectedPrimaryColor()
1786
+ }"
1787
+ [style]="{
1788
+ 'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
1789
+ }"
1790
+ [title]="primaryColor.name"
1791
+ (click)="updateColors($event, 'primary', primaryColor)"></button>
1792
+ }
1793
+ </div>
1794
+ </div>
1795
+ <div>
1796
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.surface' | translate }}</span>
1797
+ <div class="pt-2 flex gap-2 flex-wrap justify-start">
1798
+ @for (surface of surfaceColors(); track surface.name) {
1799
+ <button
1800
+ class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1801
+ id="oip-app-configurator-surface-color-{{ surface.name }}"
1802
+ type="button"
1803
+ [ngClass]="{
1804
+ 'outline-primary': selectedSurfaceColor()
1805
+ ? selectedSurfaceColor() === surface.name
1806
+ : layoutService.layoutConfig().darkTheme
1807
+ ? surface.name === 'zinc'
1808
+ : surface.name === 'slate'
1809
+ }"
1810
+ [style]="{
1811
+ 'background-color': surface?.name === 'noir' ? 'var(--text-color)' : surface?.palette?.['500']
1812
+ }"
1813
+ [title]="surface.name"
1814
+ (click)="updateColors($event, 'surface', surface)"></button>
1815
+ }
1816
+ </div>
1817
+ </div>
1818
+ <div class="flex flex-col gap-2">
1819
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1820
+ <p-selectButton
1821
+ id="oip-app-configurator-preset-select-button"
1822
+ size="small"
1823
+ [allowEmpty]="false"
1824
+ [ngModel]="selectedPreset()"
1825
+ [options]="presets"
1826
+ optionLabel="label"
1827
+ optionValue="value"
1828
+ (ngModelChange)="onPresetChange($event)" />
1829
+ </div>
1830
+ @if (showMenuModeButton()) {
1831
+ <div class="flex flex-col gap-2">
1832
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.menuMode' | translate }}</span>
1833
+ <p-selectButton
1834
+ id="oip-app-configurator-menu-mode-select-button"
1835
+ size="small"
1836
+ [allowEmpty]="false"
1837
+ [ngModel]="menuMode()"
1838
+ [options]="menuModeOptions"
1839
+ (ngModelChange)="onMenuModeChange($event)" />
1840
+ </div>
1841
+ }
1842
+ </div>
1843
+ `,
1844
+ host: {
1845
+ class: 'hidden absolute top-[3.25rem] right-0 w-72 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]'
1846
+ }
1847
+ }]
1848
+ }] });
1639
1849
 
1640
1850
  /**
1641
1851
  * UserService is responsible for retrieving and handling user-related data,
@@ -2088,18 +2298,15 @@ class HttpClient {
2088
2298
  this.layoutService = inject(LayoutService);
2089
2299
  this.baseUrl = "";
2090
2300
  this.securityData = null;
2091
- this.securityWorker = (securityData) => {
2092
- const headers = {
2301
+ this.securityWorker = (securityData) => ({
2302
+ headers: {
2093
2303
  "Accept-language": this.layoutService.language()
2094
2304
  ? this.layoutService.language()
2095
2305
  : "en",
2096
2306
  "X-Timezone": this.layoutService.timeZone(),
2097
- };
2098
- if (securityData) {
2099
- headers.Authorization = `Bearer ${securityData}`;
2100
- }
2101
- return { headers };
2102
- };
2307
+ Authorization: `Bearer ${securityData}`,
2308
+ },
2309
+ });
2103
2310
  this.abortControllers = new Map();
2104
2311
  this.customFetch = (...fetchParams) => fetch(...fetchParams);
2105
2312
  this.baseApiParams = {
@@ -3519,85 +3726,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3519
3726
  }]
3520
3727
  }] });
3521
3728
 
3522
- /**
3523
- * Service for managing translation loading in the application
3524
- */
3525
- class L10nService {
3526
- constructor() {
3527
- this.loadedTranslations = new Set();
3528
- this.httpClient = inject(HttpClient$1);
3529
- this.translateService = inject(TranslateService);
3530
- this.primeNg = inject(PrimeNG);
3531
- this.layoutService = inject(LayoutService);
3532
- }
3533
- /**
3534
- * Loads translations for a specific component
3535
- * @param component - Name of the component to load translations for
3536
- */
3537
- loadComponentTranslations(component) {
3538
- const lang = this.translateService.currentLang;
3539
- this.loadTranslations(component, lang);
3540
- }
3541
- /**
3542
- * Gets the translated value of a key (or an array of keys)
3543
- * @returns the translated key, or an object of translated keys
3544
- */
3545
- get(key) {
3546
- this.loadComponentTranslations(key.split('.')[0]);
3547
- return this.translateService.get(key);
3548
- }
3549
- /**
3550
- * Internal method to load translations from JSON files
3551
- * @param component - Component or translation namespace
3552
- * @param lang - Language code to load translations for
3553
- */
3554
- loadTranslations(component, lang) {
3555
- const key = `${component}.${lang}`;
3556
- if (this.loadedTranslations.has(key)) {
3557
- return;
3558
- }
3559
- try {
3560
- this.httpClient.get(`./assets/i18n/${component}.${lang}.json`).subscribe((translations) => {
3561
- const current = this.translateService.translations[lang] || {};
3562
- this.translateService.setTranslation(lang, { ...current, ...translations }, true);
3563
- this.loadedTranslations.add(key);
3564
- });
3565
- }
3566
- catch (e) {
3567
- console.error(`No translations found for ${component}.${lang}.json`);
3568
- console.error(e);
3569
- }
3570
- }
3571
- /**
3572
- * Changes the lang currently used
3573
- */
3574
- use(selectedLanguage, key = null) {
3575
- if (key) {
3576
- this.get(key);
3577
- }
3578
- this.translateService.use(selectedLanguage);
3579
- }
3580
- init(languages) {
3581
- this.availableLanguages = languages;
3582
- this.translateService.addLangs(languages.map((x) => x.code));
3583
- const lang = this.layoutService.language() ? this.layoutService.language() : 'en';
3584
- this.translateService.setDefaultLang(lang);
3585
- this.translateService.use(lang).subscribe(() => {
3586
- this.loadComponentTranslations('app-info');
3587
- this.translateService.get('primeng').subscribe((res) => this.primeNg.setTranslation(res));
3588
- });
3589
- }
3590
- instant(key, interpolateParams) {
3591
- return this.translateService.instant(key, interpolateParams);
3592
- }
3593
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3594
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, providedIn: 'root' }); }
3595
- }
3596
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, decorators: [{
3597
- type: Injectable,
3598
- args: [{ providedIn: 'root' }]
3599
- }] });
3600
-
3601
3729
  class NotfoundComponent {
3602
3730
  constructor(l10nService) {
3603
3731
  l10nService.get('notfound');
@@ -4125,7 +4253,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4125
4253
  class DbMigrationComponent extends BaseModuleComponent {
4126
4254
  constructor() {
4127
4255
  super();
4128
- this.l10nService = inject(L10nService);
4129
4256
  this.l10nService.loadComponentTranslations('db-migration');
4130
4257
  }
4131
4258
  async ngOnInit() {
@@ -4150,84 +4277,84 @@ class DbMigrationComponent extends BaseModuleComponent {
4150
4277
  return this.baseDataService.sendRequest(`api/${this.controller}/apply-migration`, 'POST', request);
4151
4278
  }
4152
4279
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4153
- 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: `
4154
- @if (isContent) {
4155
- <div class="card" style="height: 100%">
4156
- <p-confirmDialog />
4157
- <div>
4158
- <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4159
- <div class="flex flex-row gap-2">
4160
- <p-button
4161
- icon="pi pi-refresh"
4162
- severity="secondary"
4163
- tooltipPosition="bottom"
4164
- [outlined]="true"
4165
- [pTooltip]="'db-migration.actions.refresh' | translate"
4166
- (click)="refreshAction()" />
4167
- <p-button
4168
- icon="pi pi-filter-slash"
4169
- severity="secondary"
4170
- tooltipPosition="bottom"
4171
- [outlined]="true"
4172
- [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4173
- (click)="dt.clear()" />
4174
- </div>
4175
- <div>
4176
- <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4177
- <ng-template let-columns pTemplate="header">
4178
- <tr>
4179
- <th pSortableColumn="name" scope="col">
4180
- {{ 'db-migration.columns.name' | translate }}
4181
- <p-columnFilter display="menu" field="name" type="text" />
4182
- </th>
4183
- <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4184
- <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4185
- <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4186
- <th scope="col"></th>
4187
- </tr>
4188
- </ng-template>
4189
-
4190
- <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4191
- <tr [pEditableRow]="rowData">
4192
- <td>
4193
- {{ rowData.name }}
4194
- </td>
4195
- <td>
4196
- @if (rowData.applied) {
4197
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"> </p-button>
4198
- }
4199
- </td>
4200
- <td>
4201
- @if (rowData.exist) {
4202
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
4203
- }
4204
- </td>
4205
- <td>
4206
- @if (rowData.pending) {
4207
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4208
- }
4209
- </td>
4210
- <td>
4211
- <p-button
4212
- icon="pi pi-bolt"
4213
- pCancelEditableRow
4214
- pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4215
- severity="secondary"
4216
- tooltipPosition="left"
4217
- [rounded]="true"
4218
- [text]="true"
4219
- (click)="applyMigration(rowData)">
4220
- </p-button>
4221
- </td>
4222
- </tr>
4223
- </ng-template>
4224
- </p-table>
4225
- </div>
4226
- </div>
4227
- </div>
4228
- } @else if (isSecurity) {
4229
- <security [controller]="controller" [id]="id" />
4230
- }
4280
+ 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
+ @if (isContent) {
4282
+ <div class="card" style="height: 100%">
4283
+ <p-confirmDialog/>
4284
+ <div>
4285
+ <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4286
+ <div class="flex flex-row gap-2">
4287
+ <p-button
4288
+ icon="pi pi-refresh"
4289
+ severity="secondary"
4290
+ tooltipPosition="bottom"
4291
+ [outlined]="true"
4292
+ [pTooltip]="'db-migration.actions.refresh' | translate"
4293
+ (click)="refreshAction()"/>
4294
+ <p-button
4295
+ icon="pi pi-filter-slash"
4296
+ severity="secondary"
4297
+ tooltipPosition="bottom"
4298
+ [outlined]="true"
4299
+ [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4300
+ (click)="dt.clear()"/>
4301
+ </div>
4302
+ <div>
4303
+ <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4304
+ <ng-template let-columns pTemplate="header">
4305
+ <tr>
4306
+ <th pSortableColumn="name" scope="col">
4307
+ {{ 'db-migration.columns.name' | translate }}
4308
+ <p-columnFilter display="menu" field="name" type="text"/>
4309
+ </th>
4310
+ <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4311
+ <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4312
+ <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4313
+ <th scope="col"></th>
4314
+ </tr>
4315
+ </ng-template>
4316
+
4317
+ <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4318
+ <tr [pEditableRow]="rowData">
4319
+ <td>
4320
+ {{ rowData.name }}
4321
+ </td>
4322
+ <td>
4323
+ @if (rowData.applied) {
4324
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4325
+ }
4326
+ </td>
4327
+ <td>
4328
+ @if (rowData.exist) {
4329
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
4330
+ }
4331
+ </td>
4332
+ <td>
4333
+ @if (rowData.pending) {
4334
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4335
+ }
4336
+ </td>
4337
+ <td>
4338
+ <p-button
4339
+ icon="pi pi-bolt"
4340
+ pCancelEditableRow
4341
+ pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4342
+ severity="secondary"
4343
+ tooltipPosition="left"
4344
+ [rounded]="true"
4345
+ [text]="true"
4346
+ (click)="applyMigration(rowData)">
4347
+ </p-button>
4348
+ </td>
4349
+ </tr>
4350
+ </ng-template>
4351
+ </p-table>
4352
+ </div>
4353
+ </div>
4354
+ </div>
4355
+ } @else if (isSecurity) {
4356
+ <security [controller]="controller" [id]="id"/>
4357
+ }
4231
4358
  `, 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" }] }); }
4232
4359
  }
4233
4360
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, decorators: [{
@@ -4247,84 +4374,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4247
4374
  TranslatePipe
4248
4375
  ],
4249
4376
  selector: 'db-migration',
4250
- template: `
4251
- @if (isContent) {
4252
- <div class="card" style="height: 100%">
4253
- <p-confirmDialog />
4254
- <div>
4255
- <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4256
- <div class="flex flex-row gap-2">
4257
- <p-button
4258
- icon="pi pi-refresh"
4259
- severity="secondary"
4260
- tooltipPosition="bottom"
4261
- [outlined]="true"
4262
- [pTooltip]="'db-migration.actions.refresh' | translate"
4263
- (click)="refreshAction()" />
4264
- <p-button
4265
- icon="pi pi-filter-slash"
4266
- severity="secondary"
4267
- tooltipPosition="bottom"
4268
- [outlined]="true"
4269
- [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4270
- (click)="dt.clear()" />
4271
- </div>
4272
- <div>
4273
- <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4274
- <ng-template let-columns pTemplate="header">
4275
- <tr>
4276
- <th pSortableColumn="name" scope="col">
4277
- {{ 'db-migration.columns.name' | translate }}
4278
- <p-columnFilter display="menu" field="name" type="text" />
4279
- </th>
4280
- <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4281
- <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4282
- <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4283
- <th scope="col"></th>
4284
- </tr>
4285
- </ng-template>
4286
-
4287
- <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4288
- <tr [pEditableRow]="rowData">
4289
- <td>
4290
- {{ rowData.name }}
4291
- </td>
4292
- <td>
4293
- @if (rowData.applied) {
4294
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"> </p-button>
4295
- }
4296
- </td>
4297
- <td>
4298
- @if (rowData.exist) {
4299
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
4300
- }
4301
- </td>
4302
- <td>
4303
- @if (rowData.pending) {
4304
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4305
- }
4306
- </td>
4307
- <td>
4308
- <p-button
4309
- icon="pi pi-bolt"
4310
- pCancelEditableRow
4311
- pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4312
- severity="secondary"
4313
- tooltipPosition="left"
4314
- [rounded]="true"
4315
- [text]="true"
4316
- (click)="applyMigration(rowData)">
4317
- </p-button>
4318
- </td>
4319
- </tr>
4320
- </ng-template>
4321
- </p-table>
4322
- </div>
4323
- </div>
4324
- </div>
4325
- } @else if (isSecurity) {
4326
- <security [controller]="controller" [id]="id" />
4327
- }
4377
+ template: `
4378
+ @if (isContent) {
4379
+ <div class="card" style="height: 100%">
4380
+ <p-confirmDialog/>
4381
+ <div>
4382
+ <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4383
+ <div class="flex flex-row gap-2">
4384
+ <p-button
4385
+ icon="pi pi-refresh"
4386
+ severity="secondary"
4387
+ tooltipPosition="bottom"
4388
+ [outlined]="true"
4389
+ [pTooltip]="'db-migration.actions.refresh' | translate"
4390
+ (click)="refreshAction()"/>
4391
+ <p-button
4392
+ icon="pi pi-filter-slash"
4393
+ severity="secondary"
4394
+ tooltipPosition="bottom"
4395
+ [outlined]="true"
4396
+ [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4397
+ (click)="dt.clear()"/>
4398
+ </div>
4399
+ <div>
4400
+ <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4401
+ <ng-template let-columns pTemplate="header">
4402
+ <tr>
4403
+ <th pSortableColumn="name" scope="col">
4404
+ {{ 'db-migration.columns.name' | translate }}
4405
+ <p-columnFilter display="menu" field="name" type="text"/>
4406
+ </th>
4407
+ <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4408
+ <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4409
+ <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4410
+ <th scope="col"></th>
4411
+ </tr>
4412
+ </ng-template>
4413
+
4414
+ <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4415
+ <tr [pEditableRow]="rowData">
4416
+ <td>
4417
+ {{ rowData.name }}
4418
+ </td>
4419
+ <td>
4420
+ @if (rowData.applied) {
4421
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4422
+ }
4423
+ </td>
4424
+ <td>
4425
+ @if (rowData.exist) {
4426
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
4427
+ }
4428
+ </td>
4429
+ <td>
4430
+ @if (rowData.pending) {
4431
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4432
+ }
4433
+ </td>
4434
+ <td>
4435
+ <p-button
4436
+ icon="pi pi-bolt"
4437
+ pCancelEditableRow
4438
+ pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4439
+ severity="secondary"
4440
+ tooltipPosition="left"
4441
+ [rounded]="true"
4442
+ [text]="true"
4443
+ (click)="applyMigration(rowData)">
4444
+ </p-button>
4445
+ </td>
4446
+ </tr>
4447
+ </ng-template>
4448
+ </p-table>
4449
+ </div>
4450
+ </div>
4451
+ </div>
4452
+ } @else if (isSecurity) {
4453
+ <security [controller]="controller" [id]="id"/>
4454
+ }
4328
4455
  `,
4329
4456
  providers: [ConfirmationService]
4330
4457
  }]
@@ -5954,6 +6081,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5954
6081
  args: [{ required: true }]
5955
6082
  }] } });
5956
6083
 
6084
+ class IframeModuleComponent extends BaseModuleComponent {
6085
+ constructor() {
6086
+ super();
6087
+ this.renderer = inject(Renderer2);
6088
+ this.translate = inject(TranslateService);
6089
+ this.iframeUrl = null;
6090
+ this.l10nService.loadComponentTranslations('iframe-module');
6091
+ }
6092
+ set iframeElement(element) {
6093
+ this.iframe = element;
6094
+ this.updateIframeSrc();
6095
+ }
6096
+ setIframeUrl(url) {
6097
+ const iframeUrl = url?.trim();
6098
+ if (!iframeUrl) {
6099
+ this.iframeUrl = null;
6100
+ this.updateIframeSrc();
6101
+ const message = this.translate.instant('iframe-module.iframeModule.emptyUrlMessage');
6102
+ this.msgService.warn(message);
6103
+ return;
6104
+ }
6105
+ if (!this.isAllowedIframeUrl(iframeUrl)) {
6106
+ this.iframeUrl = null;
6107
+ this.updateIframeSrc();
6108
+ const message = this.translate.instant('iframe-module.iframeModule.siteLoadingMessage');
6109
+ this.msgService.error(message);
6110
+ return;
6111
+ }
6112
+ this.iframeUrl = iframeUrl;
6113
+ this.updateIframeSrc();
6114
+ }
6115
+ isAllowedIframeUrl(url) {
6116
+ try {
6117
+ const parsedUrl = new URL(url, window.location.origin);
6118
+ return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
6119
+ }
6120
+ catch {
6121
+ return false;
6122
+ }
6123
+ }
6124
+ async onModuleInstanceChange() {
6125
+ this.setIframeUrl(this.settings.url);
6126
+ }
6127
+ updateIframeSrc() {
6128
+ if (!this.iframe) {
6129
+ return;
6130
+ }
6131
+ if (!this.iframeUrl) {
6132
+ this.renderer.removeAttribute(this.iframe.nativeElement, 'src');
6133
+ return;
6134
+ }
6135
+ this.renderer.setAttribute(this.iframe.nativeElement, 'src', this.iframeUrl);
6136
+ }
6137
+ onIframeError() {
6138
+ const errorMessage = this.translate.instant('iframe-module.iframeModule.siteLoadingMessage');
6139
+ this.msgService.error(errorMessage);
6140
+ }
6141
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: IframeModuleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6142
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: IframeModuleComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "iframeElement", first: true, predicate: ["iframe"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
6143
+ @if (isContent) {
6144
+ <iframe
6145
+ #iframe
6146
+ class="w-full h-screen"
6147
+ style="background-color: var(--surface-ground)"
6148
+ title="Main iframe"
6149
+ (error)="onIframeError()">
6150
+ </iframe>
6151
+ } @else if (isSettings) {
6152
+ <div class="flex flex-col md:flex-row gap-8">
6153
+ <div class="md:w-1/2">
6154
+ <div class="card flex flex-col gap-4">
6155
+ <div class="font-semibold text-xl">{{ 'baseComponent.settings' | translate }}</div>
6156
+ <div class="col-span-30 md:col-span-10 w-full">
6157
+ <input
6158
+ class="w-full"
6159
+ pInputText
6160
+ placeholder="{{ 'iframe-module.iframeModule.urlPlaceholder' | translate }}"
6161
+ qa-id="iframe-module-settings-site-url-input"
6162
+ type="text"
6163
+ [(ngModel)]="settings.url"/>
6164
+ </div>
6165
+ <div class="flex justify-end">
6166
+ <p-button
6167
+ icon="pi pi-save"
6168
+ label="{{ 'iframe-module.iframeModule.settingSaveButtonLabel' | translate }}"
6169
+ qa-id="iframe-module-settings-save-button"
6170
+ (onClick)="saveSettings(settings)">
6171
+ </p-button>
6172
+ </div>
6173
+ </div>
6174
+ </div>
6175
+ </div>
6176
+ } @else if (isSecurity) {
6177
+ <security [controller]="controller" [id]="id"/>
6178
+ }
6179
+ `, isInline: true, dependencies: [{ kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "directive", type: 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: "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: "pipe", type: TranslatePipe, name: "translate" }] }); }
6180
+ }
6181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: IframeModuleComponent, decorators: [{
6182
+ type: Component,
6183
+ args: [{
6184
+ standalone: true,
6185
+ imports: [SecurityComponent, TranslatePipe, InputText, FormsModule, Button],
6186
+ template: `
6187
+ @if (isContent) {
6188
+ <iframe
6189
+ #iframe
6190
+ class="w-full h-screen"
6191
+ style="background-color: var(--surface-ground)"
6192
+ title="Main iframe"
6193
+ (error)="onIframeError()">
6194
+ </iframe>
6195
+ } @else if (isSettings) {
6196
+ <div class="flex flex-col md:flex-row gap-8">
6197
+ <div class="md:w-1/2">
6198
+ <div class="card flex flex-col gap-4">
6199
+ <div class="font-semibold text-xl">{{ 'baseComponent.settings' | translate }}</div>
6200
+ <div class="col-span-30 md:col-span-10 w-full">
6201
+ <input
6202
+ class="w-full"
6203
+ pInputText
6204
+ placeholder="{{ 'iframe-module.iframeModule.urlPlaceholder' | translate }}"
6205
+ qa-id="iframe-module-settings-site-url-input"
6206
+ type="text"
6207
+ [(ngModel)]="settings.url"/>
6208
+ </div>
6209
+ <div class="flex justify-end">
6210
+ <p-button
6211
+ icon="pi pi-save"
6212
+ label="{{ 'iframe-module.iframeModule.settingSaveButtonLabel' | translate }}"
6213
+ qa-id="iframe-module-settings-save-button"
6214
+ (onClick)="saveSettings(settings)">
6215
+ </p-button>
6216
+ </div>
6217
+ </div>
6218
+ </div>
6219
+ </div>
6220
+ } @else if (isSecurity) {
6221
+ <security [controller]="controller" [id]="id"/>
6222
+ }
6223
+ `
6224
+ }]
6225
+ }], ctorParameters: () => [], propDecorators: { iframeElement: [{
6226
+ type: ViewChild,
6227
+ args: ['iframe']
6228
+ }] } });
6229
+
5957
6230
  /**
5958
6231
  * A route guard that ensures the user is authenticated and has a valid access token.
5959
6232
  * If the access token is expired, it attempts to refresh the session.
@@ -6182,6 +6455,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6182
6455
  }]
6183
6456
  }] });
6184
6457
 
6458
+ const appendCurrentOriginSecureRoute = (secureRoutes) => {
6459
+ const currentOriginRoute = `${window.location.origin}/`;
6460
+ return Array.from(new Set([...(secureRoutes ?? []), currentOriginRoute]));
6461
+ };
6185
6462
  /**
6186
6463
  * Load keycloak settings from backend and save to sessionStorage
6187
6464
  * @param httpClient
@@ -6208,7 +6485,7 @@ const httpLoaderAuthFactory = (httpClient) => {
6208
6485
  useRefreshToken: config.useRefreshToken,
6209
6486
  silentRenew: config.silentRenew,
6210
6487
  logLevel: config.logLevel,
6211
- secureRoutes: config.secureRoutes
6488
+ secureRoutes: appendCurrentOriginSecureRoute(config.secureRoutes)
6212
6489
  };
6213
6490
  sessionStorage.setItem(KEYCLOAK_SETTINGS_KEY, JSON.stringify(authConfig));
6214
6491
  return authConfig;
@@ -6223,5 +6500,5 @@ const httpLoaderAuthFactory = (httpClient) => {
6223
6500
  * Generated bundle index. Do not edit.
6224
6501
  */
6225
6502
 
6226
- 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, 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 };
6503
+ 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 };
6227
6504
  //# sourceMappingURL=oip-common.mjs.map