oip-common 0.1.6 → 0.1.8

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, 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';
@@ -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,11 +1153,31 @@ 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: `
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: `
858
1181
  <div class="flex flex-col md:flex-row gap-8">
859
1182
  <div class="md:w-1/2">
860
1183
  <div class="card flex flex-col gap-4">
@@ -869,7 +1192,7 @@ class SecurityComponent {
869
1192
  </label>
870
1193
  <p-multiSelect
871
1194
  id="oip-security-multiselect-{{ item.name }}"
872
- placeholder="Select roles"
1195
+ placeholder="{{ 'securityComponent.selectRoles' | translate }}"
873
1196
  [maxSelectedLabels]="10"
874
1197
  [options]="roles"
875
1198
  [(ngModel)]="item.roles" />
@@ -907,7 +1230,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
907
1230
  </label>
908
1231
  <p-multiSelect
909
1232
  id="oip-security-multiselect-{{ item.name }}"
910
- placeholder="Select roles"
1233
+ placeholder="{{ 'securityComponent.selectRoles' | translate }}"
911
1234
  [maxSelectedLabels]="10"
912
1235
  [options]="roles"
913
1236
  [(ngModel)]="item.roles" />
@@ -934,11 +1257,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
934
1257
  type: Input
935
1258
  }] } });
936
1259
 
937
- const presets = {
938
- Aura,
939
- Lara,
940
- Nora
941
- };
1260
+ const APP_THEME_PRESETS = new InjectionToken('APP_THEME_PRESETS', {
1261
+ factory: () => []
1262
+ });
1263
+ const APP_THEME_PRESETS_MERGE_MODE = new InjectionToken('APP_THEME_PRESETS_MERGE_MODE', {
1264
+ factory: () => 'mergeWithDefaults'
1265
+ });
1266
+
1267
+ const DEFAULT_THEME_PRESETS = [
1268
+ { id: 'Aura', label: 'Aura', preset: Aura },
1269
+ { id: 'Lara', label: 'Lara', preset: Lara },
1270
+ { id: 'Nora', label: 'Nora', preset: Nora }
1271
+ ];
1272
+ const PRIMARY_COLORS = [
1273
+ 'emerald',
1274
+ 'green',
1275
+ 'lime',
1276
+ 'orange',
1277
+ 'amber',
1278
+ 'yellow',
1279
+ 'teal',
1280
+ 'cyan',
1281
+ 'sky',
1282
+ 'blue',
1283
+ 'indigo',
1284
+ 'violet',
1285
+ 'purple',
1286
+ 'fuchsia',
1287
+ 'pink',
1288
+ 'rose'
1289
+ ];
942
1290
  class AppConfiguratorComponent {
943
1291
  constructor() {
944
1292
  this.router = inject(Router);
@@ -946,7 +1294,14 @@ class AppConfiguratorComponent {
946
1294
  this.layoutService = inject(LayoutService);
947
1295
  this.platformId = inject(PLATFORM_ID);
948
1296
  this.primeng = inject(PrimeNG);
949
- this.presets = Object.keys(presets);
1297
+ this.injectedThemePresets = inject(APP_THEME_PRESETS);
1298
+ this.themePresetMergeMode = inject(APP_THEME_PRESETS_MERGE_MODE);
1299
+ this.themePresets = this.getThemePresets();
1300
+ this.themePresetsMap = new Map(this.themePresets.map((theme) => [theme.id, theme]));
1301
+ this.defaultThemePreset = this.themePresets[0] ?? DEFAULT_THEME_PRESETS[0];
1302
+ this.fallbackPrimaryColors = (DEFAULT_THEME_PRESETS[0].preset
1303
+ .primitive ?? {});
1304
+ this.presets = this.themePresets.map((theme) => ({ label: theme.label ?? theme.id, value: theme.id }));
950
1305
  this.showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
951
1306
  this.menuModeOptions = [
952
1307
  { label: 'Static', value: 'static' },
@@ -1097,43 +1452,23 @@ class AppConfiguratorComponent {
1097
1452
  this.selectedPreset = computed(() => this.layoutService.layoutConfig().preset, ...(ngDevMode ? [{ debugName: "selectedPreset" }] : []));
1098
1453
  this.menuMode = computed(() => this.layoutService.layoutConfig().menuMode, ...(ngDevMode ? [{ debugName: "menuMode" }] : []));
1099
1454
  this.primaryColors = computed(() => {
1100
- const presetPalette = presets[this.layoutService.layoutConfig().preset].primitive;
1101
- const colors = [
1102
- 'emerald',
1103
- 'green',
1104
- 'lime',
1105
- 'orange',
1106
- 'amber',
1107
- 'yellow',
1108
- 'teal',
1109
- 'cyan',
1110
- 'sky',
1111
- 'blue',
1112
- 'indigo',
1113
- 'violet',
1114
- 'purple',
1115
- 'fuchsia',
1116
- 'pink',
1117
- 'rose'
1118
- ];
1119
- const palettes = [{ name: 'noir', palette: {} }];
1120
- colors.forEach((color) => {
1121
- palettes.push({
1122
- name: color,
1123
- palette: presetPalette?.[color]
1124
- });
1125
- });
1126
- return palettes;
1455
+ const activeThemePreset = this.getThemeById(this.layoutService.layoutConfig().preset);
1456
+ return this.getPrimaryColorOptions(activeThemePreset);
1127
1457
  }, ...(ngDevMode ? [{ debugName: "primaryColors" }] : []));
1458
+ this.surfaceColors = computed(() => {
1459
+ const activeThemePreset = this.getThemeById(this.layoutService.layoutConfig().preset);
1460
+ return this.getSurfaceColorOptions(activeThemePreset);
1461
+ }, ...(ngDevMode ? [{ debugName: "surfaceColors" }] : []));
1128
1462
  }
1129
1463
  ngOnInit() {
1130
1464
  if (isPlatformBrowser(this.platformId)) {
1131
- this.onPresetChange(this.layoutService.layoutConfig().preset);
1465
+ const presetId = this.ensureValidThemeId(this.layoutService.layoutConfig().preset);
1466
+ this.onPresetChange(presetId);
1132
1467
  }
1133
1468
  }
1134
1469
  getPresetExt() {
1135
1470
  const color = this.primaryColors().find((c) => c.name === this.selectedPrimaryColor()) || {};
1136
- const preset = this.layoutService.layoutConfig().preset;
1471
+ const preset = this.getThemeById(this.layoutService.layoutConfig().preset).id;
1137
1472
  if (color.name === 'noir') {
1138
1473
  return {
1139
1474
  semantic: {
@@ -1258,307 +1593,259 @@ class AppConfiguratorComponent {
1258
1593
  };
1259
1594
  }
1260
1595
  }
1261
- updateColors(event, type, color) {
1262
- if (type === 'primary') {
1263
- this.layoutService.layoutConfig.update((state) => ({
1264
- ...state,
1265
- primary: color.name
1266
- }));
1267
- }
1268
- else if (type === 'surface') {
1269
- this.layoutService.layoutConfig.update((state) => ({
1270
- ...state,
1271
- surface: color.name
1272
- }));
1273
- }
1274
- this.applyTheme(type, color);
1275
- event.stopPropagation();
1276
- }
1277
- applyTheme(type, color) {
1278
- if (type === 'primary') {
1279
- updatePreset(this.getPresetExt());
1280
- }
1281
- else if (type === 'surface') {
1282
- updateSurfacePalette(color.palette);
1596
+ getThemePresets() {
1597
+ const source = this.themePresetMergeMode === 'replaceDefaults' ? [] : [...DEFAULT_THEME_PRESETS];
1598
+ const result = [...source];
1599
+ for (const theme of this.injectedThemePresets ?? []) {
1600
+ if (!theme?.id || !theme?.preset) {
1601
+ continue;
1602
+ }
1603
+ const index = result.findIndex((item) => item.id === theme.id);
1604
+ if (index >= 0) {
1605
+ result[index] = theme;
1606
+ }
1607
+ else {
1608
+ result.push(theme);
1609
+ }
1283
1610
  }
1611
+ return result;
1284
1612
  }
1285
- onPresetChange(event) {
1286
- this.layoutService.layoutConfig.update((state) => ({
1287
- ...state,
1288
- preset: event
1289
- }));
1290
- const preset = presets[event];
1291
- const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
1292
- $t().preset(preset).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
1293
- }
1294
- onMenuModeChange(event) {
1295
- this.layoutService.layoutConfig.update((prev) => ({
1296
- ...prev,
1297
- menuMode: event
1298
- }));
1299
- }
1300
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppConfiguratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1301
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AppConfiguratorComponent, isStandalone: true, selector: "app-configurator", host: { classAttribute: "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)]" }, ngImport: i0, template: `
1302
- <div class="flex flex-col gap-4">
1303
- <div>
1304
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.primary' | translate }}</span>
1305
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1306
- @for (primaryColor of primaryColors(); track primaryColor.name) {
1307
- <button
1308
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1309
- id="oip-app-configurator-primary-color-{{ primaryColor.name }}"
1310
- type="button"
1311
- [ngClass]="{
1312
- 'outline-primary': primaryColor.name === selectedPrimaryColor()
1313
- }"
1314
- [style]="{
1315
- 'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
1316
- }"
1317
- [title]="primaryColor.name"
1318
- (click)="updateColors($event, 'primary', primaryColor)"></button>
1319
- }
1320
- </div>
1321
- </div>
1322
- <div>
1323
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.surface' | translate }}</span>
1324
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1325
- @for (surface of surfaces; track surface.name) {
1326
- <button
1327
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1328
- id="oip-app-configurator-surface-color-{{ surface.name }}"
1329
- type="button"
1330
- [ngClass]="{
1331
- 'outline-primary': selectedSurfaceColor()
1332
- ? selectedSurfaceColor() === surface.name
1333
- : layoutService.layoutConfig().darkTheme
1334
- ? surface.name === 'zinc'
1335
- : surface.name === 'slate'
1336
- }"
1337
- [style]="{
1338
- 'background-color': surface?.name === 'noir' ? 'var(--text-color)' : surface?.palette?.['500']
1339
- }"
1340
- [title]="surface.name"
1341
- (click)="updateColors($event, 'surface', surface)"></button>
1342
- }
1343
- </div>
1344
- </div>
1345
- <div class="flex flex-col gap-2">
1346
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1347
- <p-selectButton
1348
- id="oip-app-configurator-preset-select-button"
1349
- size="small"
1350
- [allowEmpty]="false"
1351
- [ngModel]="selectedPreset()"
1352
- [options]="presets"
1353
- (ngModelChange)="onPresetChange($event)" />
1354
- </div>
1355
- @if (showMenuModeButton()) {
1356
- <div class="flex flex-col gap-2">
1357
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.menuMode' | translate }}</span>
1358
- <p-selectButton
1359
- id="oip-app-configurator-menu-mode-select-button"
1360
- size="small"
1361
- [allowEmpty]="false"
1362
- [ngModel]="menuMode()"
1363
- [options]="menuModeOptions"
1364
- (ngModelChange)="onMenuModeChange($event)" />
1365
- </div>
1366
- }
1367
- </div>
1368
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
1369
- }
1370
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppConfiguratorComponent, decorators: [{
1371
- type: Component,
1372
- args: [{
1373
- selector: 'app-configurator',
1374
- standalone: true,
1375
- imports: [CommonModule, FormsModule, SelectButtonModule, TranslatePipe],
1376
- template: `
1377
- <div class="flex flex-col gap-4">
1378
- <div>
1379
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.primary' | translate }}</span>
1380
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1381
- @for (primaryColor of primaryColors(); track primaryColor.name) {
1382
- <button
1383
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1384
- id="oip-app-configurator-primary-color-{{ primaryColor.name }}"
1385
- type="button"
1386
- [ngClass]="{
1387
- 'outline-primary': primaryColor.name === selectedPrimaryColor()
1388
- }"
1389
- [style]="{
1390
- 'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
1391
- }"
1392
- [title]="primaryColor.name"
1393
- (click)="updateColors($event, 'primary', primaryColor)"></button>
1394
- }
1395
- </div>
1396
- </div>
1397
- <div>
1398
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.surface' | translate }}</span>
1399
- <div class="pt-2 flex gap-2 flex-wrap justify-start">
1400
- @for (surface of surfaces; track surface.name) {
1401
- <button
1402
- class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1403
- id="oip-app-configurator-surface-color-{{ surface.name }}"
1404
- type="button"
1405
- [ngClass]="{
1406
- 'outline-primary': selectedSurfaceColor()
1407
- ? selectedSurfaceColor() === surface.name
1408
- : layoutService.layoutConfig().darkTheme
1409
- ? surface.name === 'zinc'
1410
- : surface.name === 'slate'
1411
- }"
1412
- [style]="{
1413
- 'background-color': surface?.name === 'noir' ? 'var(--text-color)' : surface?.palette?.['500']
1414
- }"
1415
- [title]="surface.name"
1416
- (click)="updateColors($event, 'surface', surface)"></button>
1417
- }
1418
- </div>
1419
- </div>
1420
- <div class="flex flex-col gap-2">
1421
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1422
- <p-selectButton
1423
- id="oip-app-configurator-preset-select-button"
1424
- size="small"
1425
- [allowEmpty]="false"
1426
- [ngModel]="selectedPreset()"
1427
- [options]="presets"
1428
- (ngModelChange)="onPresetChange($event)" />
1429
- </div>
1430
- @if (showMenuModeButton()) {
1431
- <div class="flex flex-col gap-2">
1432
- <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.menuMode' | translate }}</span>
1433
- <p-selectButton
1434
- id="oip-app-configurator-menu-mode-select-button"
1435
- size="small"
1436
- [allowEmpty]="false"
1437
- [ngModel]="menuMode()"
1438
- [options]="menuModeOptions"
1439
- (ngModelChange)="onMenuModeChange($event)" />
1440
- </div>
1441
- }
1442
- </div>
1443
- `,
1444
- host: {
1445
- 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)]'
1446
- }
1447
- }]
1448
- }] });
1449
-
1450
- class SecurityService {
1451
- }
1452
- /**
1453
- * SecurityService extends OidcSecurityService to manage authentication,
1454
- * token handling, and user role access in an Angular application.
1455
- *
1456
- * It provides helper methods for checking authentication, managing tokens,
1457
- * determining user roles, and performing logout and refresh operations.
1458
- */
1459
- class KeycloakSecurityService extends OidcSecurityService {
1460
- /**
1461
- * Initializes service and subscribes to authentication events.
1462
- * When a 'NewAuthenticationResult' event is received, the `auth` method is called.
1463
- */
1464
- constructor() {
1465
- super();
1466
- /**
1467
- * Handles angular OIDC events.
1468
- */
1469
- this.publicEventsService = inject(PublicEventsService);
1470
- /**
1471
- * Stores the latest login response from checkAuth().
1472
- */
1473
- this.loginResponse = new BehaviorSubject(null);
1474
- /**
1475
- * Stores the decoded access token payload.
1476
- */
1477
- this.payload = new BehaviorSubject(null);
1478
- /**
1479
- * Stores user-specific data from the login response.
1480
- */
1481
- this.currentUser = new BehaviorSubject(null);
1482
- /**
1483
- * Emits access token updates from initial auth check, manual refresh,
1484
- * and library authentication events.
1485
- */
1486
- this.accessToken = new ReplaySubject(1);
1487
- this.publicEventsService
1488
- .registerForEvents()
1489
- .pipe(filter((event) => event.type === EventTypes.NewAuthenticationResult))
1490
- .subscribe(() => {
1491
- super.getAccessToken().subscribe(token => { this.accessToken.next(token); });
1492
- this.auth();
1493
- });
1494
- }
1495
- getCurrentUser() {
1496
- return this.currentUser.getValue();
1497
- }
1498
- getCurrentUser$() {
1499
- return this.currentUser.asObservable();
1500
- }
1501
- /**
1502
- * Returns the ID token for the sign-in.
1503
- * @returns A string with the id token.
1504
- */
1505
- getAccessToken(configId) {
1506
- return merge(super.getAccessToken(configId), this.accessToken.asObservable()).pipe(distinctUntilChanged());
1507
- }
1508
- /**
1509
- * Indicates whether the current user has the 'admin' role.
1510
- *
1511
- * @returns {boolean} True if the user is an admin, false otherwise.
1512
- */
1513
- isAdmin() {
1514
- return this.payload.getValue()?.realm_access?.roles?.includes('admin');
1515
- }
1516
- /**
1517
- * Initiates authentication check and updates login response, user data,
1518
- * and decoded token payload if authenticated.
1519
- */
1520
- auth() {
1521
- super.checkAuth().subscribe((_response) => {
1522
- this.loginResponse.next(_response);
1523
- this.currentUser.next(_response.userData);
1524
- this.getPayloadFromAccessToken().subscribe((_token) => {
1525
- this.payload.next(_token);
1613
+ getThemeById(themeId) {
1614
+ return this.themePresetsMap.get(themeId ?? '') ?? this.defaultThemePreset;
1615
+ }
1616
+ getPrimaryColorOptions(activeThemePreset) {
1617
+ if (activeThemePreset.primaryColors) {
1618
+ return Object.entries(activeThemePreset.primaryColors)
1619
+ .filter((entry) => Boolean(entry[1]))
1620
+ .map(([name, palette]) => ({ name, palette }));
1621
+ }
1622
+ const presetPalette = (activeThemePreset.preset.primitive ?? {});
1623
+ const palettes = [{ name: 'noir', palette: {} }];
1624
+ PRIMARY_COLORS.forEach((color) => {
1625
+ palettes.push({
1626
+ name: color,
1627
+ palette: presetPalette[color] ?? this.fallbackPrimaryColors[color]
1526
1628
  });
1527
1629
  });
1630
+ return palettes;
1528
1631
  }
1529
- /**
1530
- * Performs logout and clears the local token payload.
1531
- *
1532
- * @param {string} [configId] Optional configuration ID for logout.
1533
- * @param {LogoutAuthOptions} [logoutAuthOptions] Optional logout options.
1534
- */
1535
- logout(configId, logoutAuthOptions) {
1536
- this.logoff(configId, logoutAuthOptions).subscribe((x) => this.payload.next(x));
1632
+ getSurfaceColorOptions(activeThemePreset) {
1633
+ if (activeThemePreset.surfaceColors) {
1634
+ return Object.entries(activeThemePreset.surfaceColors)
1635
+ .filter((entry) => Boolean(entry[1]))
1636
+ .map(([name, palette]) => ({ name, palette }));
1637
+ }
1638
+ return this.surfaces;
1537
1639
  }
1538
- /**
1539
- * Completes the BehaviorSubjects when the service is destroyed to avoid memory leaks.
1540
- */
1541
- ngOnDestroy() {
1542
- this.loginResponse.complete();
1543
- this.payload.complete();
1544
- this.currentUser.complete();
1640
+ ensureValidThemeId(themeId) {
1641
+ if (themeId && this.themePresetsMap.has(themeId)) {
1642
+ return themeId;
1643
+ }
1644
+ if (themeId && isPlatformBrowser(this.platformId)) {
1645
+ console.warn(`[AppConfigurator] Unknown theme preset "${themeId}", fallback to "${this.defaultThemePreset.id}".`);
1646
+ }
1647
+ return this.defaultThemePreset.id;
1545
1648
  }
1546
- /**
1547
- * Checks whether the current access token is expired based on the 'exp' claim.
1548
- *
1549
- * @returns {Observable<boolean>} Observable that emits true if the token is expired.
1550
- */
1551
- isTokenExpired() {
1552
- return this.getPayloadFromAccessToken().pipe(map((payload) => {
1553
- return payload.exp < Math.floor(Date.now() / 1000);
1649
+ updateColors(event, type, color) {
1650
+ if (type === 'primary') {
1651
+ this.layoutService.layoutConfig.update((state) => ({
1652
+ ...state,
1653
+ primary: color.name
1654
+ }));
1655
+ }
1656
+ else if (type === 'surface') {
1657
+ this.layoutService.layoutConfig.update((state) => ({
1658
+ ...state,
1659
+ surface: color.name
1660
+ }));
1661
+ }
1662
+ this.applyTheme(type, color);
1663
+ event.stopPropagation();
1664
+ }
1665
+ applyTheme(type, color) {
1666
+ if (type === 'primary') {
1667
+ updatePreset(this.getPresetExt());
1668
+ }
1669
+ else if (type === 'surface') {
1670
+ updateSurfacePalette(color.palette);
1671
+ }
1672
+ }
1673
+ onPresetChange(event) {
1674
+ const nextThemeId = this.ensureValidThemeId(event);
1675
+ const nextTheme = this.getThemeById(nextThemeId);
1676
+ const primaryColors = this.getPrimaryColorOptions(nextTheme);
1677
+ const surfaceColors = this.getSurfaceColorOptions(nextTheme);
1678
+ this.layoutService.layoutConfig.update((state) => ({
1679
+ ...state,
1680
+ preset: nextThemeId,
1681
+ primary: primaryColors.some((color) => color.name === state.primary) ? state.primary : (primaryColors[0]?.name ?? state.primary),
1682
+ surface: nextTheme.surfaceColors && !surfaceColors.some((color) => color.name === state.surface)
1683
+ ? (surfaceColors[0]?.name ?? state.surface)
1684
+ : state.surface
1554
1685
  }));
1686
+ const preset = nextTheme.preset;
1687
+ const surfacePalette = this.surfaceColors().find((s) => s.name === this.selectedSurfaceColor())?.palette;
1688
+ $t().preset(preset).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
1555
1689
  }
1556
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1557
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService }); }
1690
+ onMenuModeChange(event) {
1691
+ this.layoutService.layoutConfig.update((prev) => ({
1692
+ ...prev,
1693
+ menuMode: event
1694
+ }));
1695
+ }
1696
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppConfiguratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1697
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AppConfiguratorComponent, isStandalone: true, selector: "app-configurator", host: { classAttribute: "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)]" }, ngImport: i0, template: `
1698
+ <div class="flex flex-col gap-4">
1699
+ <div>
1700
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.primary' | translate }}</span>
1701
+ <div class="pt-2 flex gap-2 flex-wrap justify-start">
1702
+ @for (primaryColor of primaryColors(); track primaryColor.name) {
1703
+ <button
1704
+ class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1705
+ id="oip-app-configurator-primary-color-{{ primaryColor.name }}"
1706
+ type="button"
1707
+ [ngClass]="{
1708
+ 'outline-primary': primaryColor.name === selectedPrimaryColor()
1709
+ }"
1710
+ [style]="{
1711
+ 'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
1712
+ }"
1713
+ [title]="primaryColor.name"
1714
+ (click)="updateColors($event, 'primary', primaryColor)"></button>
1715
+ }
1716
+ </div>
1717
+ </div>
1718
+ <div>
1719
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.surface' | translate }}</span>
1720
+ <div class="pt-2 flex gap-2 flex-wrap justify-start">
1721
+ @for (surface of surfaceColors(); track surface.name) {
1722
+ <button
1723
+ class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
1724
+ id="oip-app-configurator-surface-color-{{ surface.name }}"
1725
+ type="button"
1726
+ [ngClass]="{
1727
+ 'outline-primary': selectedSurfaceColor()
1728
+ ? selectedSurfaceColor() === surface.name
1729
+ : layoutService.layoutConfig().darkTheme
1730
+ ? surface.name === 'zinc'
1731
+ : surface.name === 'slate'
1732
+ }"
1733
+ [style]="{
1734
+ 'background-color': surface?.name === 'noir' ? 'var(--text-color)' : surface?.palette?.['500']
1735
+ }"
1736
+ [title]="surface.name"
1737
+ (click)="updateColors($event, 'surface', surface)"></button>
1738
+ }
1739
+ </div>
1740
+ </div>
1741
+ <div class="flex flex-col gap-2">
1742
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1743
+ <p-selectButton
1744
+ id="oip-app-configurator-preset-select-button"
1745
+ size="small"
1746
+ [allowEmpty]="false"
1747
+ [ngModel]="selectedPreset()"
1748
+ [options]="presets"
1749
+ optionLabel="label"
1750
+ optionValue="value"
1751
+ (ngModelChange)="onPresetChange($event)" />
1752
+ </div>
1753
+ @if (showMenuModeButton()) {
1754
+ <div class="flex flex-col gap-2">
1755
+ <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.menuMode' | translate }}</span>
1756
+ <p-selectButton
1757
+ id="oip-app-configurator-menu-mode-select-button"
1758
+ size="small"
1759
+ [allowEmpty]="false"
1760
+ [ngModel]="menuMode()"
1761
+ [options]="menuModeOptions"
1762
+ (ngModelChange)="onMenuModeChange($event)" />
1763
+ </div>
1764
+ }
1765
+ </div>
1766
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
1558
1767
  }
1559
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: KeycloakSecurityService, decorators: [{
1560
- type: Injectable
1561
- }], ctorParameters: () => [] });
1768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AppConfiguratorComponent, decorators: [{
1769
+ type: Component,
1770
+ args: [{
1771
+ selector: 'app-configurator',
1772
+ standalone: true,
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
+ }] });
1562
1849
 
1563
1850
  /**
1564
1851
  * UserService is responsible for retrieving and handling user-related data,
@@ -2011,18 +2298,15 @@ class HttpClient {
2011
2298
  this.layoutService = inject(LayoutService);
2012
2299
  this.baseUrl = "";
2013
2300
  this.securityData = null;
2014
- this.securityWorker = (securityData) => {
2015
- const headers = {
2301
+ this.securityWorker = (securityData) => ({
2302
+ headers: {
2016
2303
  "Accept-language": this.layoutService.language()
2017
2304
  ? this.layoutService.language()
2018
2305
  : "en",
2019
2306
  "X-Timezone": this.layoutService.timeZone(),
2020
- };
2021
- if (securityData) {
2022
- headers.Authorization = `Bearer ${securityData}`;
2023
- }
2024
- return { headers };
2025
- };
2307
+ Authorization: `Bearer ${securityData}`,
2308
+ },
2309
+ });
2026
2310
  this.abortControllers = new Map();
2027
2311
  this.customFetch = (...fetchParams) => fetch(...fetchParams);
2028
2312
  this.baseApiParams = {
@@ -3442,85 +3726,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3442
3726
  }]
3443
3727
  }] });
3444
3728
 
3445
- /**
3446
- * Service for managing translation loading in the application
3447
- */
3448
- class L10nService {
3449
- constructor() {
3450
- this.loadedTranslations = new Set();
3451
- this.httpClient = inject(HttpClient$1);
3452
- this.translateService = inject(TranslateService);
3453
- this.primeNg = inject(PrimeNG);
3454
- this.layoutService = inject(LayoutService);
3455
- }
3456
- /**
3457
- * Loads translations for a specific component
3458
- * @param component - Name of the component to load translations for
3459
- */
3460
- loadComponentTranslations(component) {
3461
- const lang = this.translateService.currentLang;
3462
- this.loadTranslations(component, lang);
3463
- }
3464
- /**
3465
- * Gets the translated value of a key (or an array of keys)
3466
- * @returns the translated key, or an object of translated keys
3467
- */
3468
- get(key) {
3469
- this.loadComponentTranslations(key.split('.')[0]);
3470
- return this.translateService.get(key);
3471
- }
3472
- /**
3473
- * Internal method to load translations from JSON files
3474
- * @param component - Component or translation namespace
3475
- * @param lang - Language code to load translations for
3476
- */
3477
- loadTranslations(component, lang) {
3478
- const key = `${component}.${lang}`;
3479
- if (this.loadedTranslations.has(key)) {
3480
- return;
3481
- }
3482
- try {
3483
- this.httpClient.get(`./assets/i18n/${component}.${lang}.json`).subscribe((translations) => {
3484
- const current = this.translateService.translations[lang] || {};
3485
- this.translateService.setTranslation(lang, { ...current, ...translations }, true);
3486
- this.loadedTranslations.add(key);
3487
- });
3488
- }
3489
- catch (e) {
3490
- console.error(`No translations found for ${component}.${lang}.json`);
3491
- console.error(e);
3492
- }
3493
- }
3494
- /**
3495
- * Changes the lang currently used
3496
- */
3497
- use(selectedLanguage, key = null) {
3498
- if (key) {
3499
- this.get(key);
3500
- }
3501
- this.translateService.use(selectedLanguage);
3502
- }
3503
- init(languages) {
3504
- this.availableLanguages = languages;
3505
- this.translateService.addLangs(languages.map((x) => x.code));
3506
- const lang = this.layoutService.language() ? this.layoutService.language() : 'en';
3507
- this.translateService.setDefaultLang(lang);
3508
- this.translateService.use(lang).subscribe(() => {
3509
- this.loadComponentTranslations('app-info');
3510
- this.translateService.get('primeng').subscribe((res) => this.primeNg.setTranslation(res));
3511
- });
3512
- }
3513
- instant(key, interpolateParams) {
3514
- return this.translateService.instant(key, interpolateParams);
3515
- }
3516
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3517
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, providedIn: 'root' }); }
3518
- }
3519
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: L10nService, decorators: [{
3520
- type: Injectable,
3521
- args: [{ providedIn: 'root' }]
3522
- }] });
3523
-
3524
3729
  class NotfoundComponent {
3525
3730
  constructor(l10nService) {
3526
3731
  l10nService.get('notfound');
@@ -4048,7 +4253,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4048
4253
  class DbMigrationComponent extends BaseModuleComponent {
4049
4254
  constructor() {
4050
4255
  super();
4051
- this.l10nService = inject(L10nService);
4052
4256
  this.l10nService.loadComponentTranslations('db-migration');
4053
4257
  }
4054
4258
  async ngOnInit() {
@@ -4073,84 +4277,84 @@ class DbMigrationComponent extends BaseModuleComponent {
4073
4277
  return this.baseDataService.sendRequest(`api/${this.controller}/apply-migration`, 'POST', request);
4074
4278
  }
4075
4279
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4076
- 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: `
4077
- @if (isContent) {
4078
- <div class="card" style="height: 100%">
4079
- <p-confirmDialog />
4080
- <div>
4081
- <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4082
- <div class="flex flex-row gap-2">
4083
- <p-button
4084
- icon="pi pi-refresh"
4085
- severity="secondary"
4086
- tooltipPosition="bottom"
4087
- [outlined]="true"
4088
- [pTooltip]="'db-migration.actions.refresh' | translate"
4089
- (click)="refreshAction()" />
4090
- <p-button
4091
- icon="pi pi-filter-slash"
4092
- severity="secondary"
4093
- tooltipPosition="bottom"
4094
- [outlined]="true"
4095
- [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4096
- (click)="dt.clear()" />
4097
- </div>
4098
- <div>
4099
- <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4100
- <ng-template let-columns pTemplate="header">
4101
- <tr>
4102
- <th pSortableColumn="name" scope="col">
4103
- {{ 'db-migration.columns.name' | translate }}
4104
- <p-columnFilter display="menu" field="name" type="text" />
4105
- </th>
4106
- <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4107
- <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4108
- <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4109
- <th scope="col"></th>
4110
- </tr>
4111
- </ng-template>
4112
-
4113
- <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4114
- <tr [pEditableRow]="rowData">
4115
- <td>
4116
- {{ rowData.name }}
4117
- </td>
4118
- <td>
4119
- @if (rowData.applied) {
4120
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"> </p-button>
4121
- }
4122
- </td>
4123
- <td>
4124
- @if (rowData.exist) {
4125
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
4126
- }
4127
- </td>
4128
- <td>
4129
- @if (rowData.pending) {
4130
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4131
- }
4132
- </td>
4133
- <td>
4134
- <p-button
4135
- icon="pi pi-bolt"
4136
- pCancelEditableRow
4137
- pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4138
- severity="secondary"
4139
- tooltipPosition="left"
4140
- [rounded]="true"
4141
- [text]="true"
4142
- (click)="applyMigration(rowData)">
4143
- </p-button>
4144
- </td>
4145
- </tr>
4146
- </ng-template>
4147
- </p-table>
4148
- </div>
4149
- </div>
4150
- </div>
4151
- } @else if (isSecurity) {
4152
- <security [controller]="controller" [id]="id" />
4153
- }
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
+ }
4154
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" }] }); }
4155
4359
  }
4156
4360
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DbMigrationComponent, decorators: [{
@@ -4170,84 +4374,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4170
4374
  TranslatePipe
4171
4375
  ],
4172
4376
  selector: 'db-migration',
4173
- template: `
4174
- @if (isContent) {
4175
- <div class="card" style="height: 100%">
4176
- <p-confirmDialog />
4177
- <div>
4178
- <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
4179
- <div class="flex flex-row gap-2">
4180
- <p-button
4181
- icon="pi pi-refresh"
4182
- severity="secondary"
4183
- tooltipPosition="bottom"
4184
- [outlined]="true"
4185
- [pTooltip]="'db-migration.actions.refresh' | translate"
4186
- (click)="refreshAction()" />
4187
- <p-button
4188
- icon="pi pi-filter-slash"
4189
- severity="secondary"
4190
- tooltipPosition="bottom"
4191
- [outlined]="true"
4192
- [pTooltip]="'db-migration.actions.cleanFilter' | translate"
4193
- (click)="dt.clear()" />
4194
- </div>
4195
- <div>
4196
- <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
4197
- <ng-template let-columns pTemplate="header">
4198
- <tr>
4199
- <th pSortableColumn="name" scope="col">
4200
- {{ 'db-migration.columns.name' | translate }}
4201
- <p-columnFilter display="menu" field="name" type="text" />
4202
- </th>
4203
- <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
4204
- <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
4205
- <th scope="col">{{ 'db-migration.columns.pending' | translate }}</th>
4206
- <th scope="col"></th>
4207
- </tr>
4208
- </ng-template>
4209
-
4210
- <ng-template #body let-columns="columns" let-editing="editing" let-ri="rowIndex" let-rowData>
4211
- <tr [pEditableRow]="rowData">
4212
- <td>
4213
- {{ rowData.name }}
4214
- </td>
4215
- <td>
4216
- @if (rowData.applied) {
4217
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"> </p-button>
4218
- }
4219
- </td>
4220
- <td>
4221
- @if (rowData.exist) {
4222
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
4223
- }
4224
- </td>
4225
- <td>
4226
- @if (rowData.pending) {
4227
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"></p-button>
4228
- }
4229
- </td>
4230
- <td>
4231
- <p-button
4232
- icon="pi pi-bolt"
4233
- pCancelEditableRow
4234
- pTooltip="{{ 'db-migration.actions.applyMigration' | translate }}"
4235
- severity="secondary"
4236
- tooltipPosition="left"
4237
- [rounded]="true"
4238
- [text]="true"
4239
- (click)="applyMigration(rowData)">
4240
- </p-button>
4241
- </td>
4242
- </tr>
4243
- </ng-template>
4244
- </p-table>
4245
- </div>
4246
- </div>
4247
- </div>
4248
- } @else if (isSecurity) {
4249
- <security [controller]="controller" [id]="id" />
4250
- }
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
+ }
4251
4455
  `,
4252
4456
  providers: [ConfirmationService]
4253
4457
  }]
@@ -6060,6 +6264,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6060
6264
  type: Injectable
6061
6265
  }] });
6062
6266
 
6267
+ function provideAppThemes(themes, options) {
6268
+ const mode = options?.mode ?? 'mergeWithDefaults';
6269
+ return makeEnvironmentProviders([
6270
+ { provide: APP_THEME_PRESETS, useValue: themes },
6271
+ { provide: APP_THEME_PRESETS_MERGE_MODE, useValue: mode }
6272
+ ]);
6273
+ }
6274
+ function mergeWithDefaults(themes) {
6275
+ return provideAppThemes(themes, { mode: 'mergeWithDefaults' });
6276
+ }
6277
+ function replaceDefaults(themes) {
6278
+ return provideAppThemes(themes, { mode: 'replaceDefaults' });
6279
+ }
6280
+
6063
6281
  const langIntercept = (req, next) => {
6064
6282
  const layoutService = inject(LayoutService);
6065
6283
  const lang = layoutService.language() ? layoutService.language() : 'en';
@@ -6132,5 +6350,5 @@ const httpLoaderAuthFactory = (httpClient) => {
6132
6350
  * Generated bundle index. Do not edit.
6133
6351
  */
6134
6352
 
6135
- export { 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, provideLogoComponent };
6353
+ 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 };
6136
6354
  //# sourceMappingURL=oip-common.mjs.map