@c8y/ngx-components 1022.16.2 → 1022.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/asset-properties/asset-properties.model.d.ts +117 -0
  2. package/asset-properties/asset-properties.model.d.ts.map +1 -0
  3. package/asset-properties/asset-properties.service.d.ts +72 -0
  4. package/asset-properties/asset-properties.service.d.ts.map +1 -0
  5. package/asset-properties/asset-property-list/asset-property-action.directive.d.ts +11 -0
  6. package/asset-properties/asset-property-list/asset-property-action.directive.d.ts.map +1 -0
  7. package/asset-properties/asset-property-list/asset-property-icon.pipe.d.ts +11 -0
  8. package/asset-properties/asset-property-list/asset-property-icon.pipe.d.ts.map +1 -0
  9. package/asset-properties/asset-property-list/asset-property-list.component.d.ts +195 -0
  10. package/asset-properties/asset-property-list/asset-property-list.component.d.ts.map +1 -0
  11. package/asset-properties/asset-property-list/asset-property-value.pipe.d.ts +17 -0
  12. package/asset-properties/asset-property-list/asset-property-value.pipe.d.ts.map +1 -0
  13. package/asset-properties/asset-property-list/tree-data-source.d.ts +19 -0
  14. package/asset-properties/asset-property-list/tree-data-source.d.ts.map +1 -0
  15. package/asset-properties/asset-property-selector-drawer/asset-property-selector-drawer.component.d.ts +75 -0
  16. package/asset-properties/asset-property-selector-drawer/asset-property-selector-drawer.component.d.ts.map +1 -0
  17. package/asset-properties/c8y-ngx-components-asset-properties.d.ts.map +1 -0
  18. package/asset-properties/index.d.ts +6 -0
  19. package/asset-properties/index.d.ts.map +1 -0
  20. package/core/asset-property/asset-property.model.d.ts +0 -8
  21. package/core/asset-property/asset-property.model.d.ts.map +1 -1
  22. package/core/authentication/new-password.component.d.ts +3 -2
  23. package/core/authentication/new-password.component.d.ts.map +1 -1
  24. package/core/modal/modal.service.d.ts +1 -0
  25. package/core/modal/modal.service.d.ts.map +1 -1
  26. package/core/user/user-edit-modal.component.d.ts +1 -0
  27. package/core/user/user-edit-modal.component.d.ts.map +1 -1
  28. package/core/user/user-edit.component.d.ts +4 -1
  29. package/core/user/user-edit.component.d.ts.map +1 -1
  30. package/datapoint-explorer/datapoint-explorer.module.d.ts +2 -0
  31. package/datapoint-explorer/datapoint-explorer.module.d.ts.map +1 -1
  32. package/datapoint-explorer/devicemanagement/c8y-ngx-components-datapoint-explorer-devicemanagement.d.ts.map +1 -0
  33. package/datapoint-explorer/devicemanagement/index.d.ts +2 -0
  34. package/datapoint-explorer/devicemanagement/index.d.ts.map +1 -0
  35. package/datapoint-explorer/view/datapoint-explorer.component.d.ts +8 -3
  36. package/datapoint-explorer/view/datapoint-explorer.component.d.ts.map +1 -1
  37. package/datapoint-explorer/view/datapoint-explorer.model.d.ts +8 -0
  38. package/datapoint-explorer/view/datapoint-explorer.model.d.ts.map +1 -0
  39. package/datapoint-explorer/view/index.d.ts +1 -0
  40. package/datapoint-explorer/view/index.d.ts.map +1 -1
  41. package/device-provisioned-certificates/device-tab-provisioned-certificates.component.d.ts +8 -2
  42. package/device-provisioned-certificates/device-tab-provisioned-certificates.component.d.ts.map +1 -1
  43. package/echart/charts.component.d.ts +1 -1
  44. package/echart/charts.component.d.ts.map +1 -1
  45. package/echart/index.d.ts +1 -0
  46. package/echart/index.d.ts.map +1 -1
  47. package/echart/models/datapoints-graph-widget.model.d.ts +13 -0
  48. package/echart/models/datapoints-graph-widget.model.d.ts.map +1 -1
  49. package/echart/services/chart-helpers.service.d.ts +23 -0
  50. package/echart/services/chart-helpers.service.d.ts.map +1 -0
  51. package/echart/services/chart-realtime.service.d.ts +2 -0
  52. package/echart/services/chart-realtime.service.d.ts.map +1 -1
  53. package/echart/services/echarts-options.service.d.ts +5 -5
  54. package/echart/services/echarts-options.service.d.ts.map +1 -1
  55. package/fesm2022/c8y-ngx-components-alarms.mjs +2 -2
  56. package/fesm2022/c8y-ngx-components-alarms.mjs.map +1 -1
  57. package/fesm2022/c8y-ngx-components-asset-properties.mjs +1573 -0
  58. package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -0
  59. package/fesm2022/c8y-ngx-components-datapoint-explorer-devicemanagement.mjs +38 -0
  60. package/fesm2022/c8y-ngx-components-datapoint-explorer-devicemanagement.mjs.map +1 -0
  61. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +157 -141
  62. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
  63. package/fesm2022/c8y-ngx-components-datapoint-explorer.mjs +1 -1
  64. package/fesm2022/c8y-ngx-components-datapoint-explorer.mjs.map +1 -1
  65. package/fesm2022/c8y-ngx-components-device-list.mjs +2 -2
  66. package/fesm2022/c8y-ngx-components-device-list.mjs.map +1 -1
  67. package/fesm2022/c8y-ngx-components-device-provisioned-certificates.mjs +32 -18
  68. package/fesm2022/c8y-ngx-components-device-provisioned-certificates.mjs.map +1 -1
  69. package/fesm2022/c8y-ngx-components-echart-models.mjs +14 -1
  70. package/fesm2022/c8y-ngx-components-echart-models.mjs.map +1 -1
  71. package/fesm2022/c8y-ngx-components-echart.mjs +141 -46
  72. package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
  73. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs +3 -3
  74. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs.map +1 -1
  75. package/fesm2022/c8y-ngx-components-time-context.mjs +12 -5
  76. package/fesm2022/c8y-ngx-components-time-context.mjs.map +1 -1
  77. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs +2 -2
  78. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs.map +1 -1
  79. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs +6 -0
  80. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs.map +1 -1
  81. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +14 -6
  82. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
  83. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs +51 -10
  84. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs.map +1 -1
  85. package/fesm2022/c8y-ngx-components.mjs +1152 -1128
  86. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  87. package/locales/de.po +61 -46
  88. package/locales/es.po +44 -27
  89. package/locales/fr.po +72 -55
  90. package/locales/ja_JP.po +106 -88
  91. package/locales/ko.po +43 -27
  92. package/locales/locales.pot +49 -27
  93. package/locales/nl.po +44 -27
  94. package/locales/pl.po +44 -27
  95. package/locales/pt_BR.po +44 -27
  96. package/locales/zh_CN.po +44 -27
  97. package/locales/zh_TW.po +44 -27
  98. package/package.json +1 -1
  99. package/time-context/index.d.ts +1 -0
  100. package/time-context/index.d.ts.map +1 -1
  101. package/time-context/time-context.component.d.ts +6 -3
  102. package/time-context/time-context.component.d.ts.map +1 -1
  103. package/time-context/time-context.model.d.ts +10 -0
  104. package/time-context/time-context.model.d.ts.map +1 -0
  105. package/time-context/time-context.service.d.ts +2 -6
  106. package/time-context/time-context.service.d.ts.map +1 -1
  107. package/widgets/definitions/html-widget/html-widget-config.factory.d.ts.map +1 -1
  108. package/widgets/implementations/datapoints-graph/datapoints-graph-config/datapoints-graph-widget-config.component.d.ts +2 -0
  109. package/widgets/implementations/datapoints-graph/datapoints-graph-config/datapoints-graph-widget-config.component.d.ts.map +1 -1
  110. package/widgets/implementations/html-widget/html-widget-properties-selector/html-widget-properties-selector.component.d.ts +17 -0
  111. package/widgets/implementations/html-widget/html-widget-properties-selector/html-widget-properties-selector.component.d.ts.map +1 -0
  112. package/widgets/implementations/html-widget/html-widget.model.d.ts +2 -2
  113. package/widgets/implementations/html-widget/html-widget.model.d.ts.map +1 -1
  114. package/widgets/implementations/html-widget/index.d.ts +1 -0
  115. package/widgets/implementations/html-widget/index.d.ts.map +1 -1
  116. package/device-parameters/c8y-ngx-components-device-parameters.d.ts.map +0 -1
  117. package/device-parameters/device-parameter-details.component.d.ts +0 -22
  118. package/device-parameters/device-parameter-details.component.d.ts.map +0 -1
  119. package/device-parameters/device-parameter-value.component.d.ts +0 -12
  120. package/device-parameters/device-parameter-value.component.d.ts.map +0 -1
  121. package/device-parameters/device-parameters-list.component.d.ts +0 -23
  122. package/device-parameters/device-parameters-list.component.d.ts.map +0 -1
  123. package/device-parameters/device-parameters-tab.guard.d.ts +0 -11
  124. package/device-parameters/device-parameters-tab.guard.d.ts.map +0 -1
  125. package/device-parameters/index.d.ts +0 -7
  126. package/device-parameters/index.d.ts.map +0 -1
  127. package/device-parameters/parameter-type-cell-renderer.components.d.ts +0 -8
  128. package/device-parameters/parameter-type-cell-renderer.components.d.ts.map +0 -1
  129. package/fesm2022/c8y-ngx-components-device-parameters.mjs +0 -215
  130. package/fesm2022/c8y-ngx-components-device-parameters.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Directive, Injectable, Injector, InjectionToken, NgModuleRef, createNgModule, Optional, Inject, isDevMode, inject, Pipe, EventEmitter, NgModule, LOCALE_ID, EnvironmentInjector, createEnvironmentInjector, HostListener, Component, HostBinding, Output, forwardRef, DestroyRef, ViewChild, SecurityContext, TemplateRef, provideAppInitializer, Self, SkipSelf, Attribute, ContentChild, ViewContainerRef, ElementRef, ContentChildren, ViewChildren, createComponent, runInInjectionContext, importProvidersFrom, ChangeDetectionStrategy, SimpleChange, reflectComponentType, signal, effect, Type, computed, input, output } from '@angular/core';
2
+ import { Input, Directive, Injectable, Injector, InjectionToken, NgModuleRef, createNgModule, Optional, Inject, isDevMode, inject, Pipe, EventEmitter, NgModule, LOCALE_ID, EnvironmentInjector, createEnvironmentInjector, HostListener, Component, HostBinding, Output, forwardRef, DestroyRef, ViewChild, SecurityContext, TemplateRef, provideAppInitializer, Self, SkipSelf, Attribute, ContentChild, ViewContainerRef, ElementRef, ContentChildren, ViewChildren, viewChild, effect, createComponent, runInInjectionContext, importProvidersFrom, ChangeDetectionStrategy, SimpleChange, reflectComponentType, signal, Type, computed, input, output } from '@angular/core';
3
3
  import * as i1$3 from 'ngx-bootstrap/dropdown';
4
4
  import { BsDropdownModule, BsDropdownDirective } from 'ngx-bootstrap/dropdown';
5
5
  import { CdkTrapFocus, A11yModule } from '@angular/cdk/a11y';
@@ -19106,1296 +19106,1320 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
19106
19106
  }]
19107
19107
  }] });
19108
19108
 
19109
- /**
19110
- * Service to show a modal.
19111
- */
19112
- class ModalService {
19113
- constructor(modalService, gainsightService) {
19114
- this.modalService = modalService;
19115
- this.gainsightService = gainsightService;
19109
+ class PasswordConfirmModalComponent {
19110
+ constructor(user, ui, client, alert) {
19111
+ this.user = user;
19112
+ this.ui = ui;
19113
+ this.client = client;
19114
+ this.alert = alert;
19115
+ this.passwordConfirmedEmitter = new EventEmitter();
19116
+ this.loading = false;
19116
19117
  }
19117
- /**
19118
- * Shows a quick confirm message modal.
19119
- * @param title The title of that modal.
19120
- * @param body The text body to display.
19121
- * @param status The status.
19122
- * @param labels The labels to use. Default: { ok: 'Confirm', cancel: 'Cancel'}
19123
- * @param confirmOptions Selection options to display as checkbox list.
19124
- * @param productExperienceEvent Additional data to attach to custom product experience events.
19125
- */
19126
- async confirm(title, body, status = Status.INFO, labels = {}, confirmOptions = {}, productExperienceEvent = { eventName: 'confirmModal' }) {
19127
- const modalLabels = {
19128
- ok: labels.ok || gettext$1('Confirm'),
19129
- cancel: labels.cancel || gettext$1('Cancel')
19118
+ async passwordConfirm() {
19119
+ if (this.password) {
19120
+ const supportUserName = this.ui.currentSupportUserName.value;
19121
+ const userId = this.ui.currentUser.value.id;
19122
+ const credentials = {
19123
+ password: this.password,
19124
+ user: `${supportUserName ? `${supportUserName}$` : ''}${userId}`,
19125
+ tenant: this.client.tenant
19126
+ };
19127
+ try {
19128
+ this.loading = true;
19129
+ const auth = new BasicAuth(credentials);
19130
+ const newClient = this.createNewClient(auth, this.client.baseUrl);
19131
+ await newClient.user.current();
19132
+ this.emitSuccessAndClose();
19133
+ }
19134
+ catch (e) {
19135
+ if (e.res && e.res.status === 401 && e.data && /pin|totp/i.test(e.data.message)) {
19136
+ this.emitSuccessAndClose();
19137
+ }
19138
+ else {
19139
+ this.alert.danger(gettext$1("Provided password doesn't match your current one."));
19140
+ }
19141
+ }
19142
+ finally {
19143
+ this.loading = false;
19144
+ }
19145
+ }
19146
+ }
19147
+ cancel() {
19148
+ this.passwordConfirmedEmitter.emit(false);
19149
+ this.modal._dismiss();
19150
+ }
19151
+ createNewClient(auth, baseUrl) {
19152
+ return new Client(auth, baseUrl);
19153
+ }
19154
+ emitSuccessAndClose() {
19155
+ this.passwordConfirmedEmitter.emit(true);
19156
+ this.modal._dismiss();
19157
+ }
19158
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirmModalComponent, deps: [{ token: i1.UserService }, { token: AppStateService }, { token: i1.FetchClient }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19159
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordConfirmModalComponent, isStandalone: true, selector: "c8y-password-confirm-modal", outputs: { passwordConfirmedEmitter: "passwordConfirmedEmitter" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\r\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\r\n <div class=\"d-block p-24 p-b-0\">\r\n <c8y-form-group [hasWarning]=\"true\">\r\n <label translate for=\"currentPassword\">Enter your password</label>\r\n <input\r\n id=\"currentPassword\"\r\n [(ngModel)]=\"password\"\r\n type=\"password\"\r\n name=\"password\"\r\n class=\"form-control\"\r\n placeholder=\"{{ 'Enter your password' | translate }}\"\r\n required\r\n />\r\n <c8y-messages>\r\n <c8y-message translate>\r\n Enter the password of the user that you are currently logged in with.\r\n </c8y-message>\r\n </c8y-messages>\r\n </c8y-form-group>\r\n </div>\r\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\r\n <button\r\n title=\"{{ 'Cancel' | translate }}\"\r\n class=\"btn btn-default\"\r\n type=\"button\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ 'Cancel' | translate }}\r\n </button>\r\n <button\r\n title=\"{{ 'Confirm' | translate }}\"\r\n class=\"btn btn-primary\"\r\n type=\"submit\"\r\n [disabled]=\"!confirmForm.form.valid || loading\"\r\n >\r\n {{ 'Confirm' | translate }}\r\n </button>\r\n </div>\r\n </form>\r\n</c8y-modal>\r\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19160
+ }
19161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirmModalComponent, decorators: [{
19162
+ type: Component,
19163
+ args: [{ selector: 'c8y-password-confirm-modal', standalone: true, imports: [
19164
+ ModalComponent,
19165
+ FormsModule$1,
19166
+ FormGroupComponent,
19167
+ C8yTranslateDirective,
19168
+ RequiredInputPlaceholderDirective,
19169
+ MessagesComponent,
19170
+ MessageDirective,
19171
+ C8yTranslatePipe
19172
+ ], template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\r\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\r\n <div class=\"d-block p-24 p-b-0\">\r\n <c8y-form-group [hasWarning]=\"true\">\r\n <label translate for=\"currentPassword\">Enter your password</label>\r\n <input\r\n id=\"currentPassword\"\r\n [(ngModel)]=\"password\"\r\n type=\"password\"\r\n name=\"password\"\r\n class=\"form-control\"\r\n placeholder=\"{{ 'Enter your password' | translate }}\"\r\n required\r\n />\r\n <c8y-messages>\r\n <c8y-message translate>\r\n Enter the password of the user that you are currently logged in with.\r\n </c8y-message>\r\n </c8y-messages>\r\n </c8y-form-group>\r\n </div>\r\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\r\n <button\r\n title=\"{{ 'Cancel' | translate }}\"\r\n class=\"btn btn-default\"\r\n type=\"button\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ 'Cancel' | translate }}\r\n </button>\r\n <button\r\n title=\"{{ 'Confirm' | translate }}\"\r\n class=\"btn btn-primary\"\r\n type=\"submit\"\r\n [disabled]=\"!confirmForm.form.valid || loading\"\r\n >\r\n {{ 'Confirm' | translate }}\r\n </button>\r\n </div>\r\n </form>\r\n</c8y-modal>\r\n" }]
19173
+ }], ctorParameters: () => [{ type: i1.UserService }, { type: AppStateService }, { type: i1.FetchClient }, { type: AlertService }], propDecorators: { passwordConfirmedEmitter: [{
19174
+ type: Output
19175
+ }], modal: [{
19176
+ type: ViewChild,
19177
+ args: ['modal', { static: false }]
19178
+ }] } });
19179
+
19180
+ class CurrentPasswordModalComponent {
19181
+ constructor(modal) {
19182
+ this.modal = modal;
19183
+ this.currentPasswordEmitter = new EventEmitter();
19184
+ }
19185
+ passwordConfirm() {
19186
+ this.currentPasswordEmitter.emit(this.password);
19187
+ this.modal.hide();
19188
+ }
19189
+ cancel() {
19190
+ this.currentPasswordEmitter.emit(null);
19191
+ this.modal.hide();
19192
+ }
19193
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CurrentPasswordModalComponent, deps: [{ token: i1$7.BsModalRef }], target: i0.ɵɵFactoryTarget.Component }); }
19194
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: CurrentPasswordModalComponent, isStandalone: true, selector: "c8y-current-password-modal", outputs: { currentPasswordEmitter: "currentPasswordEmitter" }, ngImport: i0, template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\n <div class=\"d-block p-24\">\n <c8y-form-group [hasWarning]=\"true\">\n <label translate for=\"currentPassword\">Enter your password</label>\n <input\n id=\"currentPassword\"\n [(ngModel)]=\"password\"\n type=\"password\"\n name=\"password\"\n class=\"form-control\"\n required\n />\n <c8y-messages>\n <c8y-message translate>\n The password of the user that you are currently logged in with.\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n title=\"{{ 'Confirm' | translate }}\"\n class=\"btn btn-primary\"\n [attr.data-cy]=\"'confirm-current-password-confirm-button'\"\n type=\"submit\"\n [disabled]=\"!confirmForm.form.valid\"\n >\n {{ 'Confirm' | translate }}\n </button>\n </div>\n </form>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19195
+ }
19196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CurrentPasswordModalComponent, decorators: [{
19197
+ type: Component,
19198
+ args: [{ selector: 'c8y-current-password-modal', standalone: true, imports: [
19199
+ ModalComponent,
19200
+ FormsModule$1,
19201
+ FormGroupComponent,
19202
+ C8yTranslateDirective,
19203
+ RequiredInputPlaceholderDirective,
19204
+ MessagesComponent,
19205
+ MessageDirective,
19206
+ C8yTranslatePipe
19207
+ ], template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\n <div class=\"d-block p-24\">\n <c8y-form-group [hasWarning]=\"true\">\n <label translate for=\"currentPassword\">Enter your password</label>\n <input\n id=\"currentPassword\"\n [(ngModel)]=\"password\"\n type=\"password\"\n name=\"password\"\n class=\"form-control\"\n required\n />\n <c8y-messages>\n <c8y-message translate>\n The password of the user that you are currently logged in with.\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n title=\"{{ 'Confirm' | translate }}\"\n class=\"btn btn-primary\"\n [attr.data-cy]=\"'confirm-current-password-confirm-button'\"\n type=\"submit\"\n [disabled]=\"!confirmForm.form.valid\"\n >\n {{ 'Confirm' | translate }}\n </button>\n </div>\n </form>\n</c8y-modal>\n" }]
19208
+ }], ctorParameters: () => [{ type: i1$7.BsModalRef }], propDecorators: { currentPasswordEmitter: [{
19209
+ type: Output
19210
+ }] } });
19211
+
19212
+ class PasswordService {
19213
+ constructor(modalService) {
19214
+ this.modalService = modalService;
19215
+ this.DEFAULT_PASSWORD_MIN_LENGTH = 8;
19216
+ this.GREEN = {
19217
+ colorName: 'green',
19218
+ color: 'rgb(0, 128, 0)',
19219
+ description: gettext$1('strong'),
19220
+ passwordStrength: PasswordStrength.GREEN
19130
19221
  };
19131
- const modalRef = this.modalService.show(ConfirmModalComponent, {
19132
- initialState: { title, body, labels: modalLabels, status, confirmOptions },
19222
+ this.YELLOW = {
19223
+ colorName: 'yellow',
19224
+ color: 'rgb(255, 204, 51)',
19225
+ description: gettext$1('medium'),
19226
+ passwordStrength: PasswordStrength.YELLOW
19227
+ };
19228
+ this.RED = {
19229
+ colorName: 'red',
19230
+ color: 'rgb(170, 0, 51)',
19231
+ description: gettext$1('weak'),
19232
+ passwordStrength: PasswordStrength.RED
19233
+ };
19234
+ }
19235
+ confirmPassword() {
19236
+ const modalInstance = this.modalService.show(PasswordConfirmModalComponent, {
19133
19237
  ariaDescribedby: 'modal-body',
19134
- ariaLabelledBy: 'modal-title',
19135
- ignoreBackdropClick: true
19238
+ ariaLabelledBy: 'modal-title'
19136
19239
  });
19137
- if (productExperienceEvent) {
19138
- productExperienceEvent.data = { ...productExperienceEvent.data, title };
19139
- }
19140
- this.triggerEvent(modalRef.content.result, modalLabels, productExperienceEvent);
19141
- return await modalRef.content.result;
19240
+ const passwordConfirmedEmitter = modalInstance.content.passwordConfirmedEmitter;
19241
+ return passwordConfirmedEmitter.pipe(take(1));
19142
19242
  }
19143
19243
  /**
19144
- * Shows a quick acknowledge message modal.
19145
- * @param title The title of that modal.
19146
- * @param body The text body to display.
19147
- * @param status The status.
19148
- * @param acknowledgeLabel The label to use.
19149
- * @param productExperienceEvent Additional data to attach to custom product experience events.
19244
+ * Returns an observable with the password provided by user:
19245
+ * - `string` when user provided a value
19246
+ * - `null` when user cancelled the modal
19150
19247
  */
19151
- async acknowledge(title, body, status = Status.INFO, acknowledgeLabel = gettext$1('Confirm'), productExperienceEvent = { eventName: 'confirmModal' }) {
19152
- const labels = { ok: acknowledgeLabel, cancel: null };
19153
- const modalRef = this.modalService.show(ConfirmModalComponent, {
19154
- initialState: { title, body, labels, status },
19248
+ currentPassword() {
19249
+ const modalInstance = this.modalService.show(CurrentPasswordModalComponent, {
19250
+ class: 'modal-sm',
19155
19251
  ariaDescribedby: 'modal-body',
19156
- ariaLabelledBy: 'modal-title',
19157
- ignoreBackdropClick: true
19252
+ ariaLabelledBy: 'modal-title'
19158
19253
  });
19159
- if (productExperienceEvent) {
19160
- productExperienceEvent.data = { ...productExperienceEvent.data, title };
19161
- }
19162
- this.triggerEvent(modalRef.content.result, labels, productExperienceEvent);
19163
- return await modalRef.content.result;
19254
+ const currentPasswordEmitter = modalInstance.content.currentPasswordEmitter;
19255
+ return currentPasswordEmitter.pipe(take(1));
19164
19256
  }
19165
- /**
19166
- * Shows a quick logout confirmation modal.
19167
- * @param body The text body to display. Default: 'You will be logged out to apply your changes. Do you want to proceed?'
19168
- * @param status The status.
19169
- * @param labels The labels to use. Default: { ok: 'Confirm and log out', cancel: 'Cancel' }
19170
- */
19171
- async confirmLogout(body, status = Status.WARNING, labels = {}) {
19172
- const modalLabels = {
19173
- ok: labels.ok || gettext$1('Confirm and log out'),
19174
- cancel: labels.cancel || gettext$1('Cancel')
19175
- };
19176
- const modalBody = body || gettext$1('You must log out to apply your changes. Do you want to proceed?');
19177
- return await this.confirm(gettext$1('Logout required'), modalBody, status, modalLabels);
19257
+ hasLowerCase(password) {
19258
+ return password.search(/[a-z]/) !== -1;
19178
19259
  }
19179
- triggerEvent(result, labels, productExperienceEvent) {
19180
- const data = { ...productExperienceEvent.data, url: window.location.href };
19181
- result
19182
- .then(() => {
19183
- this.gainsightService.triggerEvent(productExperienceEvent.eventName, {
19184
- ...data,
19185
- result: labels.ok
19186
- });
19187
- })
19188
- .catch(() => {
19189
- this.gainsightService.triggerEvent(productExperienceEvent.eventName, {
19190
- ...data,
19191
- result: labels.cancel
19192
- });
19193
- });
19260
+ hasUpperCase(password) {
19261
+ return password.search(/[A-Z]/) !== -1;
19194
19262
  }
19195
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, deps: [{ token: i1$7.BsModalService }, { token: GainsightService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19196
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, providedIn: 'root' }); }
19263
+ hasNumbers(password) {
19264
+ return password.search(/[0-9]/) !== -1;
19265
+ }
19266
+ hasSpecialChars(password) {
19267
+ return password.search(/[^0-9a-zA-Z]+/) !== -1;
19268
+ }
19269
+ getStrengthColor(password) {
19270
+ const passwordStrength = filter$2([
19271
+ this.hasLowerCase(password),
19272
+ this.hasUpperCase(password),
19273
+ this.hasNumbers(password),
19274
+ this.hasSpecialChars(password)
19275
+ ]).length;
19276
+ if (passwordStrength > 3) {
19277
+ return this.GREEN;
19278
+ }
19279
+ else if (passwordStrength >= 3) {
19280
+ return this.YELLOW;
19281
+ }
19282
+ else {
19283
+ return this.RED;
19284
+ }
19285
+ }
19286
+ getDefaultPasswordMinLength() {
19287
+ return this.DEFAULT_PASSWORD_MIN_LENGTH;
19288
+ }
19289
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, deps: [{ token: i1$7.BsModalService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19290
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, providedIn: 'root' }); }
19197
19291
  }
19198
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, decorators: [{
19292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, decorators: [{
19199
19293
  type: Injectable,
19200
- args: [{
19201
- providedIn: 'root'
19202
- }]
19203
- }], ctorParameters: () => [{ type: i1$7.BsModalService }, { type: GainsightService }] });
19294
+ args: [{ providedIn: 'root' }]
19295
+ }], ctorParameters: () => [{ type: i1$7.BsModalService }] });
19204
19296
 
19205
- class ThemeSwitcherService {
19206
- constructor(options) {
19207
- this.options = options;
19208
- this.darkThemeClass = `c8y-dark-theme`;
19209
- this.themeOptions = [
19210
- {
19211
- label: gettext$1('Light'),
19212
- value: 'light',
19213
- icon: 'sun'
19214
- },
19215
- {
19216
- label: gettext$1('Dark'),
19217
- value: 'dark',
19218
- icon: 'moon'
19219
- },
19220
- {
19221
- label: gettext$1('System'),
19222
- value: 'system',
19223
- icon: 'imac-settings'
19224
- }
19225
- ];
19226
- this._userSelectedThemePreference$ = new BehaviorSubject(this.getCurrentThemePreference());
19227
- this._temporaryThemePreference$ = new BehaviorSubject('none');
19228
- this.userSelectedThemePreference$ = this._userSelectedThemePreference$.asObservable();
19229
- const userSelectedTheme$ = this.userSelectedThemePreference$.pipe(switchMap(preference => {
19230
- if (preference === 'system') {
19231
- return this.getUsersSystemPreferenceForTheme$();
19232
- }
19233
- return of(preference);
19234
- }));
19235
- this.disableThemeSelection$ = this._temporaryThemePreference$.pipe(map(preference => preference !== 'none'));
19236
- this.currentlyAppliedTheme$ = this._temporaryThemePreference$.pipe(switchMap(temporaryPreference => {
19237
- if (temporaryPreference !== 'none') {
19238
- return of(temporaryPreference);
19239
- }
19240
- return userSelectedTheme$;
19241
- }));
19242
- this.darkThemeAvailable$ = this.options.get$('darkThemeAvailable').pipe(map(value => !!value));
19243
- }
19244
- getCurrentThemePreference() {
19245
- const value = getThemePreference();
19246
- if (value === 'system' || value === 'dark') {
19247
- return value;
19248
- }
19249
- return 'light';
19250
- }
19251
- getUsersSystemPreferenceForTheme$() {
19252
- return fromEvent(window.matchMedia('(prefers-color-scheme: dark)'), 'change').pipe(startWith(window.matchMedia('(prefers-color-scheme: dark)')), map((e) => (e.matches ? 'dark' : 'light')));
19297
+ class TotpSetupComponent {
19298
+ get qrCodeImage() {
19299
+ return this.totpSecret ? this.totpSecret.secretQrUrl : '';
19253
19300
  }
19254
- changeUserPreference(preference) {
19255
- setThemePreference(preference);
19256
- this._userSelectedThemePreference$.next(preference);
19257
- this.applyTheme(preference);
19301
+ get secret() {
19302
+ return this.totpSecret ? this.totpSecret.rawSecret : '';
19258
19303
  }
19259
- temporaryChangeTheme(preference) {
19260
- this._temporaryThemePreference$.next(preference);
19261
- this.applyTheme(preference);
19304
+ get qrData() {
19305
+ // TODO: waiting for BE:, now we need extract it from secretQrUrl
19306
+ // https://cumulocity.atlassian.net/browse/MTM-36387
19307
+ // return this.totpSecret ? this.totpSecret.qrData : '';
19308
+ const url = new URL(this.qrCodeImage);
19309
+ const otpAuth = url.searchParams.get('chl') || url.searchParams.get('data');
19310
+ if (!otpAuth) {
19311
+ this.alert.danger(gettext$1('Failed to generate a QR code.'));
19312
+ return '';
19313
+ }
19314
+ return decodeURIComponent(otpAuth);
19262
19315
  }
19263
- resetTemporaryTheme() {
19264
- this._temporaryThemePreference$.next('none');
19265
- this.applyTheme(this.getCurrentThemePreference());
19316
+ constructor(user, alert) {
19317
+ this.user = user;
19318
+ this.alert = alert;
19266
19319
  }
19267
- applyTheme(preference) {
19268
- applyTheme(preference);
19320
+ async ngOnInit() {
19321
+ try {
19322
+ const { data } = await this.user.generateTotpSecret();
19323
+ this.totpSecret = data;
19324
+ }
19325
+ catch (ex) {
19326
+ this.alert.addServerFailure(ex);
19327
+ }
19269
19328
  }
19270
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, deps: [{ token: OptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19271
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, providedIn: 'root' }); }
19329
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpSetupComponent, deps: [{ token: i1.UserService }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19330
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TotpSetupComponent, isStandalone: true, selector: "c8y-totp-setup", ngImport: i0, template: "<div class=\"text-center\">\n <p\n class=\"m-24 m-t-16 m-b-0\"\n translate\n >\n Scan this QR code with your smartphone using the authenticator application.\n </p>\n\n <div class=\"d-flex j-c-center\">\n <qrcode\n [width]=\"180\"\n *ngIf=\"totpSecret\"\n [qrdata]=\"qrData\"\n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'svg'\"\n ></qrcode>\n </div>\n <p class=\"text-center text-muted\">\n {{ secret }}\n </p>\n</div>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QRCodeComponent, selector: "qrcode", inputs: ["allowEmptyString", "colorDark", "colorLight", "cssClass", "elementType", "errorCorrectionLevel", "imageSrc", "imageHeight", "imageWidth", "margin", "qrdata", "scale", "version", "width", "alt", "ariaLabel", "title"], outputs: ["qrCodeURL"] }] }); }
19272
19331
  }
19273
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, decorators: [{
19274
- type: Injectable,
19275
- args: [{
19276
- providedIn: 'root'
19277
- }]
19278
- }], ctorParameters: () => [{ type: OptionsService }] });
19332
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpSetupComponent, decorators: [{
19333
+ type: Component,
19334
+ args: [{ selector: 'c8y-totp-setup', standalone: true, imports: [C8yTranslateDirective, NgIf, QRCodeComponent], template: "<div class=\"text-center\">\n <p\n class=\"m-24 m-t-16 m-b-0\"\n translate\n >\n Scan this QR code with your smartphone using the authenticator application.\n </p>\n\n <div class=\"d-flex j-c-center\">\n <qrcode\n [width]=\"180\"\n *ngIf=\"totpSecret\"\n [qrdata]=\"qrData\"\n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'svg'\"\n ></qrcode>\n </div>\n <p class=\"text-center text-muted\">\n {{ secret }}\n </p>\n</div>\n" }]
19335
+ }], ctorParameters: () => [{ type: i1.UserService }, { type: AlertService }] });
19279
19336
 
19280
- class UiSettingsComponent {
19281
- constructor(translate, state, ui, userPreferences, c8yModalService, headerService, themeSwitcher) {
19282
- this.translate = translate;
19283
- this.state = state;
19284
- this.ui = ui;
19285
- this.userPreferences = userPreferences;
19286
- this.c8yModalService = c8yModalService;
19287
- this.headerService = headerService;
19288
- this.themeSwitcher = themeSwitcher;
19289
- this.destroyed$ = new Subject();
19290
- this.currentLang = this.ui.state.lang;
19291
- this.ui.state$
19292
- .pipe(filter(({ lang }) => lang !== this.currentLang), takeUntil(this.destroyed$), first$1())
19293
- .subscribe(({ lang }) => (this.currentLang = lang));
19294
- this.open$ = this.headerService.rightDrawerOpen$;
19295
- }
19296
- ngOnInit() {
19297
- this.languages = this.state.state.langs.map(l => ({
19298
- lang: l,
19299
- nativeLanguage: this.translate.getNativeLanguage(l)
19300
- }));
19301
- }
19302
- ngOnDestroy() {
19303
- this.destroyed$.next();
19304
- this.destroyed$.complete();
19337
+ class TotpChallengeComponent {
19338
+ constructor(users, alert) {
19339
+ this.users = users;
19340
+ this.alert = alert;
19341
+ /**
19342
+ * Calls the verify endpoint if set to true (default true)
19343
+ */
19344
+ this.verify = true;
19345
+ /**
19346
+ * Emits the token on success.
19347
+ */
19348
+ this.onSuccess = new EventEmitter();
19349
+ /**
19350
+ * Emits if set up two-factor authentication is canceled.
19351
+ */
19352
+ this.totpUnconfirmedEmitter = new EventEmitter();
19353
+ this.loading = false;
19354
+ this.hasError = false;
19355
+ this.isModal = false;
19356
+ this.model = {
19357
+ token: ''
19358
+ };
19305
19359
  }
19306
- async onLanguageChange(changedLang) {
19307
- if (!changedLang) {
19308
- return;
19360
+ async verifyCode() {
19361
+ try {
19362
+ this.loading = true;
19363
+ this.hasError = false;
19364
+ if (this.verify) {
19365
+ await this.users.verifyTotpCode(this.model.token);
19366
+ }
19367
+ this.onSuccess.emit(this.model.token);
19309
19368
  }
19310
- await this.translate.switchToLanguage(changedLang);
19311
- if (await this.persistLanguage(changedLang)) {
19312
- location.reload();
19369
+ catch (e) {
19370
+ this.hasError = true;
19371
+ this.alert.removeLastDanger();
19372
+ this.loading = false;
19313
19373
  }
19314
19374
  }
19315
- async persistLanguage(lang) {
19316
- let shouldReload = true;
19375
+ cancel() {
19376
+ this.totpUnconfirmedEmitter.emit();
19377
+ }
19378
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpChallengeComponent, deps: [{ token: i1.UserService }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19379
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TotpChallengeComponent, isStandalone: true, selector: "c8y-totp-challenge", inputs: { verify: "verify", loading: "loading", hasError: "hasError", isModal: "isModal" }, outputs: { onSuccess: "onSuccess", totpUnconfirmedEmitter: "totpUnconfirmedEmitter" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<form #totpForm=\"ngForm\" class=\"loginForm\" (ngSubmit)=\"verifyCode()\" novalidate>\n <div class=\"d-block p-b-0\" [ngClass]=\"isModal ? 'p-24' : 'p-t-24'\">\n <c8y-form-group\n [hasError]=\"hasError\"\n [novalidation]=\"true\"\n [ngClass]=\"{ 'p-b-24': hasError }\"\n >\n <label translate for=\"totpToken\">Verification code</label>\n\n <input\n id=\"totpToken\"\n [(ngModel)]=\"model.token\"\n name=\"totpToken\"\n type=\"text\"\n autofocus\n autocapitalize=\"off\"\n autocorrect=\"off\"\n autocomplete=\"off\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} 624327\"\n required\n />\n\n <p id=\"helpinput\" *ngIf=\"!hasError\" class=\"help-block\" translate>\n In case of key loss, please contact your platform administrator.\n </p>\n </c8y-form-group>\n </div>\n <div [ngClass]=\"isModal ? 'modal-footer separator-top bg-level-0 sticky-bottom' : 'text-center'\">\n <button\n class=\"btn btn-default\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verify' | translate }}\"\n *ngIf=\"!loading\"\n [disabled]=\"!totpForm.form.valid\"\n >\n {{ 'Verify' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary btn-pending\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verifying\u2026' | translate }}\"\n *ngIf=\"loading\"\n >\n {{ 'Verifying\u2026' | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
19380
+ }
19381
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpChallengeComponent, decorators: [{
19382
+ type: Component,
19383
+ args: [{ selector: 'c8y-totp-challenge', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
19384
+ FormsModule$1,
19385
+ NgClass,
19386
+ FormGroupComponent,
19387
+ C8yTranslateDirective,
19388
+ RequiredInputPlaceholderDirective,
19389
+ NgIf,
19390
+ C8yTranslatePipe
19391
+ ], template: "<form #totpForm=\"ngForm\" class=\"loginForm\" (ngSubmit)=\"verifyCode()\" novalidate>\n <div class=\"d-block p-b-0\" [ngClass]=\"isModal ? 'p-24' : 'p-t-24'\">\n <c8y-form-group\n [hasError]=\"hasError\"\n [novalidation]=\"true\"\n [ngClass]=\"{ 'p-b-24': hasError }\"\n >\n <label translate for=\"totpToken\">Verification code</label>\n\n <input\n id=\"totpToken\"\n [(ngModel)]=\"model.token\"\n name=\"totpToken\"\n type=\"text\"\n autofocus\n autocapitalize=\"off\"\n autocorrect=\"off\"\n autocomplete=\"off\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} 624327\"\n required\n />\n\n <p id=\"helpinput\" *ngIf=\"!hasError\" class=\"help-block\" translate>\n In case of key loss, please contact your platform administrator.\n </p>\n </c8y-form-group>\n </div>\n <div [ngClass]=\"isModal ? 'modal-footer separator-top bg-level-0 sticky-bottom' : 'text-center'\">\n <button\n class=\"btn btn-default\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verify' | translate }}\"\n *ngIf=\"!loading\"\n [disabled]=\"!totpForm.form.valid\"\n >\n {{ 'Verify' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary btn-pending\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verifying\u2026' | translate }}\"\n *ngIf=\"loading\"\n >\n {{ 'Verifying\u2026' | translate }}\n </button>\n </div>\n</form>\n" }]
19392
+ }], ctorParameters: () => [{ type: i1.UserService }, { type: AlertService }], propDecorators: { verify: [{
19393
+ type: Input
19394
+ }], onSuccess: [{
19395
+ type: Output
19396
+ }], totpUnconfirmedEmitter: [{
19397
+ type: Output
19398
+ }], loading: [{
19399
+ type: Input
19400
+ }], hasError: [{
19401
+ type: Input
19402
+ }], isModal: [{
19403
+ type: Input
19404
+ }], modal: [{
19405
+ type: ViewChild,
19406
+ args: ['modal', { static: false }]
19407
+ }] } });
19408
+
19409
+ class UserTotpSetupComponent {
19410
+ constructor(user, modalService, modal, authService) {
19411
+ this.user = user;
19412
+ this.modalService = modalService;
19413
+ this.modal = modal;
19414
+ this.authService = authService;
19415
+ }
19416
+ async totpSetupVerified() {
19417
+ await this.user.activateTotp();
19418
+ this.modal.hide();
19317
19419
  try {
19318
- await this.c8yModalService.confirm(gettext$1('Reload recommended'), gettext$1('To change the language in the entire application, we recommend you to reload the page. If you have any unsaved changes, you can reload later. How would you like to proceed?'), Status.WARNING, {
19319
- ok: gettext$1('Reload now'),
19320
- cancel: gettext$1('Reload later')
19321
- });
19420
+ await this.modalService.acknowledge(gettext$1('Logout required'), gettext$1('You must log out in order to apply your changes'), Status.WARNING, gettext$1('Log out'));
19421
+ await this.authService.logout();
19322
19422
  }
19323
19423
  catch (ex) {
19324
- shouldReload = false;
19325
- }
19326
- finally {
19327
- this.translate.saveInLocalStorage(lang);
19328
- await this.userPreferences.set('language', lang);
19329
- this.currentLang = lang;
19424
+ // intended empty
19330
19425
  }
19331
- return shouldReload;
19332
19426
  }
19333
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsComponent, deps: [{ token: TranslateService }, { token: AppStateService }, { token: AppStateService }, { token: UserPreferencesService }, { token: ModalService }, { token: HeaderService }, { token: ThemeSwitcherService }], target: i0.ɵɵFactoryTarget.Component }); }
19334
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UiSettingsComponent, isStandalone: true, selector: "c8y-ui-settings", ngImport: i0, template: "<div class=\"separator-top p-t-8 p-b-8\">\n <div class=\"c8y-right-drawer__item sticky-top\">\n <i c8yIcon=\"eyedropper\"></i>\n <span class=\"text-bold\">{{ 'UI settings' | translate }}</span>\n </div>\n\n <div\n class=\"p-l-16 p-r-16 p-b-16\"\n *ngIf=\"themeSwitcher.darkThemeAvailable$ | async\"\n >\n <p translate>Theme</p>\n <div\n class=\"c8y-switch-multistate\"\n *ngIf=\"themeSwitcher.userSelectedThemePreference$ | async as themePreference\"\n >\n <ng-container *ngFor=\"let themeOption of themeSwitcher.themeOptions; index as i\">\n <input\n [attr.aria-label]=\"themeOption.label\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n name=\"theme-switcher\"\n type=\"radio\"\n [id]=\"'theme-option-' + i\"\n [disabled]=\"themeSwitcher.disableThemeSelection$ | async\"\n [checked]=\"themePreference === themeOption.value\"\n (click)=\"themeSwitcher.changeUserPreference(themeOption.value)\"\n />\n <label\n title=\"{{ themeOption.label | translate }}\"\n [for]=\"'theme-option-' + i\"\n >\n <i [c8yIcon]=\"themeOption.icon\"></i>\n </label>\n </ng-container>\n <div class=\"c8y-switch-multistate__handle\"></div>\n </div>\n </div>\n\n <div class=\"form-group p-l-16 p-r-16\">\n <label\n for=\"userLang\"\n translate\n >\n Language\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n id=\"userLang\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n #selectLang\n [ngModel]=\"currentLang\"\n (change)=\"onLanguageChange(selectLang.value)\"\n >\n <option\n *ngFor=\"let language of languages\"\n [value]=\"language.lang\"\n >\n {{ language.nativeLanguage }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$8.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$8.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
19427
+ close() {
19428
+ this.modal.hide();
19429
+ }
19430
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserTotpSetupComponent, deps: [{ token: i1.UserService }, { token: ModalService }, { token: i1$7.BsModalRef }, { token: SimplifiedAuthService }], target: i0.ɵɵFactoryTarget.Component }); }
19431
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserTotpSetupComponent, isStandalone: true, selector: "c8y-user-totp-setup", ngImport: i0, template: "<c8y-modal\n [title]=\"'Set up two-factor authentication' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span class=\"dlt-c8y-icon-phonelink-lock\"></span>\n </ng-container>\n\n <c8y-totp-setup></c8y-totp-setup>\n <c8y-totp-challenge [isModal]=\"true\" (onSuccess)=\"totpSetupVerified()\" (totpUnconfirmedEmitter)=\"close()\"></c8y-totp-challenge>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: TotpSetupComponent, selector: "c8y-totp-setup" }, { kind: "component", type: TotpChallengeComponent, selector: "c8y-totp-challenge", inputs: ["verify", "loading", "hasError", "isModal"], outputs: ["onSuccess", "totpUnconfirmedEmitter"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19335
19432
  }
19336
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsComponent, decorators: [{
19433
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserTotpSetupComponent, decorators: [{
19337
19434
  type: Component,
19338
- args: [{ selector: 'c8y-ui-settings', standalone: true, imports: [
19339
- IconDirective,
19340
- NgIf,
19341
- C8yTranslateDirective,
19342
- NgFor,
19343
- FormsModule$1,
19344
- C8yTranslatePipe,
19345
- AsyncPipe
19346
- ], template: "<div class=\"separator-top p-t-8 p-b-8\">\n <div class=\"c8y-right-drawer__item sticky-top\">\n <i c8yIcon=\"eyedropper\"></i>\n <span class=\"text-bold\">{{ 'UI settings' | translate }}</span>\n </div>\n\n <div\n class=\"p-l-16 p-r-16 p-b-16\"\n *ngIf=\"themeSwitcher.darkThemeAvailable$ | async\"\n >\n <p translate>Theme</p>\n <div\n class=\"c8y-switch-multistate\"\n *ngIf=\"themeSwitcher.userSelectedThemePreference$ | async as themePreference\"\n >\n <ng-container *ngFor=\"let themeOption of themeSwitcher.themeOptions; index as i\">\n <input\n [attr.aria-label]=\"themeOption.label\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n name=\"theme-switcher\"\n type=\"radio\"\n [id]=\"'theme-option-' + i\"\n [disabled]=\"themeSwitcher.disableThemeSelection$ | async\"\n [checked]=\"themePreference === themeOption.value\"\n (click)=\"themeSwitcher.changeUserPreference(themeOption.value)\"\n />\n <label\n title=\"{{ themeOption.label | translate }}\"\n [for]=\"'theme-option-' + i\"\n >\n <i [c8yIcon]=\"themeOption.icon\"></i>\n </label>\n </ng-container>\n <div class=\"c8y-switch-multistate__handle\"></div>\n </div>\n </div>\n\n <div class=\"form-group p-l-16 p-r-16\">\n <label\n for=\"userLang\"\n translate\n >\n Language\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n id=\"userLang\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n #selectLang\n [ngModel]=\"currentLang\"\n (change)=\"onLanguageChange(selectLang.value)\"\n >\n <option\n *ngFor=\"let language of languages\"\n [value]=\"language.lang\"\n >\n {{ language.nativeLanguage }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n</div>\n" }]
19347
- }], ctorParameters: () => [{ type: TranslateService }, { type: AppStateService }, { type: AppStateService }, { type: UserPreferencesService }, { type: ModalService }, { type: HeaderService }, { type: ThemeSwitcherService }] });
19435
+ args: [{ selector: 'c8y-user-totp-setup', standalone: true, imports: [ModalComponent, TotpSetupComponent, TotpChallengeComponent, C8yTranslatePipe], template: "<c8y-modal\n [title]=\"'Set up two-factor authentication' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span class=\"dlt-c8y-icon-phonelink-lock\"></span>\n </ng-container>\n\n <c8y-totp-setup></c8y-totp-setup>\n <c8y-totp-challenge [isModal]=\"true\" (onSuccess)=\"totpSetupVerified()\" (totpUnconfirmedEmitter)=\"close()\"></c8y-totp-challenge>\n</c8y-modal>\n" }]
19436
+ }], ctorParameters: () => [{ type: i1.UserService }, { type: ModalService }, { type: i1$7.BsModalRef }, { type: SimplifiedAuthService }] });
19348
19437
 
19349
- class UiSettingsModule {
19350
- static providers() {
19351
- return [
19352
- hookDrawer({
19353
- component: UiSettingsComponent,
19354
- position: 'right',
19355
- priority: 90,
19356
- id: 'uiSettings'
19357
- })
19358
- ];
19359
- }
19360
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
19361
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, imports: [CommonModule, FormsModule$1, UiSettingsComponent], exports: [UiSettingsComponent] }); }
19362
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, imports: [CommonModule, FormsModule$1, UiSettingsComponent] }); }
19363
- }
19364
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, decorators: [{
19365
- type: NgModule,
19366
- args: [{
19367
- imports: [CommonModule, FormsModule$1, UiSettingsComponent],
19368
- exports: [UiSettingsComponent]
19369
- }]
19370
- }] });
19371
-
19372
- class UserMenuItemComponent {
19373
- constructor(userService, headerService) {
19374
- this.userService = userService;
19375
- this.headerService = headerService;
19376
- this.priority = 0;
19377
- this.click = new EventEmitter();
19378
- this.open$ = this.headerService.rightDrawerOpen$;
19438
+ /**
19439
+ * The component is used to display a password control that includes the functionality of hiding and showing the input value.
19440
+ */
19441
+ class PasswordInputComponent {
19442
+ constructor() {
19443
+ /**
19444
+ * Id of input
19445
+ */
19446
+ this.id = '';
19447
+ /**
19448
+ * Value for autocomplete attribute of input
19449
+ */
19450
+ this.autocomplete = 'off';
19451
+ this.value = '';
19452
+ this.disabled = false;
19453
+ this.type = 'password';
19454
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
19455
+ this.onChange = value => { };
19456
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
19457
+ this.onTouched = () => { };
19379
19458
  }
19380
- ngAfterViewInit() {
19381
- this.viewInitTimeout = setTimeout(() => this.userService.add(this));
19459
+ writeValue(value) {
19460
+ this.value = value ? value : '';
19382
19461
  }
19383
- ngOnDestroy() {
19384
- clearTimeout(this.viewInitTimeout);
19385
- this.userService.remove(this);
19462
+ registerOnChange(fn) {
19463
+ this.onChange = fn;
19386
19464
  }
19387
- onClick() {
19388
- this.click.emit(this);
19465
+ registerOnTouched(fn) {
19466
+ this.onTouched = fn;
19389
19467
  }
19390
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserMenuItemComponent, deps: [{ token: UserMenuService }, { token: HeaderService }], target: i0.ɵɵFactoryTarget.Component }); }
19391
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserMenuItemComponent, isStandalone: true, selector: "c8y-user-menu-item", inputs: { icon: "icon", label: "label", link: "link", target: "target", priority: "priority", dataCy: "dataCy" }, outputs: { click: "click" }, viewQueries: [{ propertyName: "template", first: true, predicate: ["template"], descendants: true }], ngImport: i0, template: "<ng-template #template>\n <li>\n <a\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n *ngIf=\"link\"\n [attr.href]=\"link\"\n [attr.target]=\"target\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </a>\n <button\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n type=\"button\"\n *ngIf=\"!link\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </button>\n </li>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
19468
+ setDisabledState(isDisabled) {
19469
+ this.disabled = isDisabled;
19470
+ }
19471
+ onInput($event) {
19472
+ this.value = $event.currentTarget.value;
19473
+ this.onChange(this.value);
19474
+ }
19475
+ onFocusOut() {
19476
+ this.onTouched();
19477
+ }
19478
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
19479
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordInputComponent, isStandalone: true, selector: "c8y-password-input", inputs: { id: "id", autocomplete: "autocomplete" }, providers: [
19480
+ {
19481
+ provide: NG_VALUE_ACCESSOR,
19482
+ useExisting: forwardRef(() => PasswordInputComponent),
19483
+ multi: true
19484
+ }
19485
+ ], ngImport: i0, template: "<div class=\"input-group input-group-lg input-group-password\">\n <input\n class=\"form-control input-lg\"\n [type]=\"type\"\n [value]=\"value\"\n [id]=\"id\"\n (input)=\"onInput($event)\"\n (focusout)=\"onFocusOut()\"\n [disabled]=\"disabled\"\n [autocomplete]=\"autocomplete\"\n />\n <span class=\"input-group-btn\">\n <button\n *ngIf=\"type === 'password'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Show password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'text'\"\n >\n <i class=\"dlt-c8y-icon-eye\"></i>\n </button>\n <button\n *ngIf=\"type === 'text'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Hide password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'password'\"\n >\n <i class=\"dlt-c8y-icon-eye-slash\"></i>\n </button>\n </span>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19392
19486
  }
19393
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserMenuItemComponent, decorators: [{
19487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordInputComponent, decorators: [{
19394
19488
  type: Component,
19395
- args: [{ selector: 'c8y-user-menu-item', standalone: true, imports: [NgIf, C8yTranslatePipe, AsyncPipe], template: "<ng-template #template>\n <li>\n <a\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n *ngIf=\"link\"\n [attr.href]=\"link\"\n [attr.target]=\"target\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </a>\n <button\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n type=\"button\"\n *ngIf=\"!link\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </button>\n </li>\n</ng-template>\n" }]
19396
- }], ctorParameters: () => [{ type: UserMenuService }, { type: HeaderService }], propDecorators: { icon: [{
19397
- type: Input
19398
- }], label: [{
19399
- type: Input
19400
- }], link: [{
19401
- type: Input
19402
- }], target: [{
19403
- type: Input
19404
- }], priority: [{
19489
+ args: [{ selector: 'c8y-password-input', providers: [
19490
+ {
19491
+ provide: NG_VALUE_ACCESSOR,
19492
+ useExisting: forwardRef(() => PasswordInputComponent),
19493
+ multi: true
19494
+ }
19495
+ ], standalone: true, imports: [NgIf, C8yTranslatePipe], template: "<div class=\"input-group input-group-lg input-group-password\">\n <input\n class=\"form-control input-lg\"\n [type]=\"type\"\n [value]=\"value\"\n [id]=\"id\"\n (input)=\"onInput($event)\"\n (focusout)=\"onFocusOut()\"\n [disabled]=\"disabled\"\n [autocomplete]=\"autocomplete\"\n />\n <span class=\"input-group-btn\">\n <button\n *ngIf=\"type === 'password'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Show password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'text'\"\n >\n <i class=\"dlt-c8y-icon-eye\"></i>\n </button>\n <button\n *ngIf=\"type === 'text'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Hide password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'password'\"\n >\n <i class=\"dlt-c8y-icon-eye-slash\"></i>\n </button>\n </span>\n</div>\n" }]
19496
+ }], propDecorators: { id: [{
19405
19497
  type: Input
19406
- }], dataCy: [{
19498
+ }], autocomplete: [{
19407
19499
  type: Input
19408
- }], template: [{
19409
- type: ViewChild,
19410
- args: ['template', { static: false }]
19411
- }], click: [{
19412
- type: Output
19413
19500
  }] } });
19414
19501
 
19415
- class PasswordConfirmModalComponent {
19416
- constructor(user, ui, client, alert) {
19417
- this.user = user;
19418
- this.ui = ui;
19419
- this.client = client;
19420
- this.alert = alert;
19421
- this.passwordConfirmedEmitter = new EventEmitter();
19422
- this.loading = false;
19502
+ class PasswordConfirm {
19503
+ constructor(passwordConfirm) {
19504
+ this.passwordConfirm = passwordConfirm;
19423
19505
  }
19424
- async passwordConfirm() {
19425
- if (this.password) {
19426
- const supportUserName = this.ui.currentSupportUserName.value;
19427
- const userId = this.ui.currentUser.value.id;
19428
- const credentials = {
19429
- password: this.password,
19430
- user: `${supportUserName ? `${supportUserName}$` : ''}${userId}`,
19431
- tenant: this.client.tenant
19432
- };
19433
- try {
19434
- this.loading = true;
19435
- const auth = new BasicAuth(credentials);
19436
- const newClient = this.createNewClient(auth, this.client.baseUrl);
19437
- await newClient.user.current();
19438
- this.emitSuccessAndClose();
19439
- }
19440
- catch (e) {
19441
- if (e.res && e.res.status === 401 && e.data && /pin|totp/i.test(e.data.message)) {
19442
- this.emitSuccessAndClose();
19443
- }
19444
- else {
19445
- this.alert.danger(gettext$1("Provided password doesn't match your current one."));
19446
- }
19447
- }
19448
- finally {
19449
- this.loading = false;
19450
- }
19506
+ validate(abControl) {
19507
+ const value = abControl.value;
19508
+ const controlToCompareWith = abControl.root.get(this.passwordConfirm);
19509
+ if (controlToCompareWith && value !== controlToCompareWith.value) {
19510
+ return { passwordConfirm: true };
19451
19511
  }
19512
+ return null;
19452
19513
  }
19453
- cancel() {
19454
- this.passwordConfirmedEmitter.emit(false);
19455
- this.modal._dismiss();
19456
- }
19457
- createNewClient(auth, baseUrl) {
19458
- return new Client(auth, baseUrl);
19459
- }
19460
- emitSuccessAndClose() {
19461
- this.passwordConfirmedEmitter.emit(true);
19462
- this.modal._dismiss();
19463
- }
19464
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirmModalComponent, deps: [{ token: i1.UserService }, { token: AppStateService }, { token: i1.FetchClient }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19465
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordConfirmModalComponent, isStandalone: true, selector: "c8y-password-confirm-modal", outputs: { passwordConfirmedEmitter: "passwordConfirmedEmitter" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\r\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\r\n <div class=\"d-block p-24 p-b-0\">\r\n <c8y-form-group [hasWarning]=\"true\">\r\n <label translate for=\"currentPassword\">Enter your password</label>\r\n <input\r\n id=\"currentPassword\"\r\n [(ngModel)]=\"password\"\r\n type=\"password\"\r\n name=\"password\"\r\n class=\"form-control\"\r\n placeholder=\"{{ 'Enter your password' | translate }}\"\r\n required\r\n />\r\n <c8y-messages>\r\n <c8y-message translate>\r\n Enter the password of the user that you are currently logged in with.\r\n </c8y-message>\r\n </c8y-messages>\r\n </c8y-form-group>\r\n </div>\r\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\r\n <button\r\n title=\"{{ 'Cancel' | translate }}\"\r\n class=\"btn btn-default\"\r\n type=\"button\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ 'Cancel' | translate }}\r\n </button>\r\n <button\r\n title=\"{{ 'Confirm' | translate }}\"\r\n class=\"btn btn-primary\"\r\n type=\"submit\"\r\n [disabled]=\"!confirmForm.form.valid || loading\"\r\n >\r\n {{ 'Confirm' | translate }}\r\n </button>\r\n </div>\r\n </form>\r\n</c8y-modal>\r\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19514
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirm, deps: [{ token: 'passwordConfirm', attribute: true }], target: i0.ɵɵFactoryTarget.Directive }); }
19515
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.14", type: PasswordConfirm, isStandalone: true, selector: "[passwordConfirm]", providers: [
19516
+ {
19517
+ provide: NG_VALIDATORS,
19518
+ useExisting: PasswordConfirm,
19519
+ multi: true
19520
+ }
19521
+ ], ngImport: i0 }); }
19466
19522
  }
19467
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirmModalComponent, decorators: [{
19468
- type: Component,
19469
- args: [{ selector: 'c8y-password-confirm-modal', standalone: true, imports: [
19470
- ModalComponent,
19471
- FormsModule$1,
19472
- FormGroupComponent,
19473
- C8yTranslateDirective,
19474
- RequiredInputPlaceholderDirective,
19475
- MessagesComponent,
19476
- MessageDirective,
19477
- C8yTranslatePipe
19478
- ], template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\r\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\r\n <div class=\"d-block p-24 p-b-0\">\r\n <c8y-form-group [hasWarning]=\"true\">\r\n <label translate for=\"currentPassword\">Enter your password</label>\r\n <input\r\n id=\"currentPassword\"\r\n [(ngModel)]=\"password\"\r\n type=\"password\"\r\n name=\"password\"\r\n class=\"form-control\"\r\n placeholder=\"{{ 'Enter your password' | translate }}\"\r\n required\r\n />\r\n <c8y-messages>\r\n <c8y-message translate>\r\n Enter the password of the user that you are currently logged in with.\r\n </c8y-message>\r\n </c8y-messages>\r\n </c8y-form-group>\r\n </div>\r\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\r\n <button\r\n title=\"{{ 'Cancel' | translate }}\"\r\n class=\"btn btn-default\"\r\n type=\"button\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ 'Cancel' | translate }}\r\n </button>\r\n <button\r\n title=\"{{ 'Confirm' | translate }}\"\r\n class=\"btn btn-primary\"\r\n type=\"submit\"\r\n [disabled]=\"!confirmForm.form.valid || loading\"\r\n >\r\n {{ 'Confirm' | translate }}\r\n </button>\r\n </div>\r\n </form>\r\n</c8y-modal>\r\n" }]
19479
- }], ctorParameters: () => [{ type: i1.UserService }, { type: AppStateService }, { type: i1.FetchClient }, { type: AlertService }], propDecorators: { passwordConfirmedEmitter: [{
19480
- type: Output
19481
- }], modal: [{
19482
- type: ViewChild,
19483
- args: ['modal', { static: false }]
19484
- }] } });
19523
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirm, decorators: [{
19524
+ type: Directive,
19525
+ args: [{
19526
+ selector: '[passwordConfirm]',
19527
+ providers: [
19528
+ {
19529
+ provide: NG_VALIDATORS,
19530
+ useExisting: PasswordConfirm,
19531
+ multi: true
19532
+ }
19533
+ ],
19534
+ standalone: true
19535
+ }]
19536
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
19537
+ type: Attribute,
19538
+ args: ['passwordConfirm']
19539
+ }] }] });
19485
19540
 
19486
- class CurrentPasswordModalComponent {
19487
- constructor(modal) {
19488
- this.modal = modal;
19489
- this.currentPasswordEmitter = new EventEmitter();
19541
+ class PasswordStrengthService {
19542
+ constructor(ui) {
19543
+ this.ui = ui;
19544
+ this.GREEN_MIN_LENGTH_DEFAULT = 8;
19545
+ this.passwordStrengthSetting = {
19546
+ enforcePasswordStrength: false,
19547
+ greenMinLength: this.GREEN_MIN_LENGTH_DEFAULT,
19548
+ passwordStrengthValidity: false
19549
+ };
19490
19550
  }
19491
- passwordConfirm() {
19492
- this.currentPasswordEmitter.emit(this.password);
19493
- this.modal.hide();
19551
+ /**
19552
+ * Gets the minimal number of characters that a password should have to be considered a “green” strong one.
19553
+ * @return The min length for password or default value.
19554
+ */
19555
+ async getGreenMinLength() {
19556
+ const { greenMinLength } = (await this.getBasicAuthLoginOption()) || { greenMinLength: null };
19557
+ this.passwordStrengthSetting.greenMinLength = greenMinLength || this.GREEN_MIN_LENGTH_DEFAULT;
19558
+ return this.passwordStrengthSetting.greenMinLength;
19494
19559
  }
19495
- cancel() {
19496
- this.currentPasswordEmitter.emit(null);
19497
- this.modal.hide();
19498
- }
19499
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CurrentPasswordModalComponent, deps: [{ token: i1$7.BsModalRef }], target: i0.ɵɵFactoryTarget.Component }); }
19500
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: CurrentPasswordModalComponent, isStandalone: true, selector: "c8y-current-password-modal", outputs: { currentPasswordEmitter: "currentPasswordEmitter" }, ngImport: i0, template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\n <div class=\"d-block p-24\">\n <c8y-form-group [hasWarning]=\"true\">\n <label translate for=\"currentPassword\">Enter your password</label>\n <input\n id=\"currentPassword\"\n [(ngModel)]=\"password\"\n type=\"password\"\n name=\"password\"\n class=\"form-control\"\n required\n />\n <c8y-messages>\n <c8y-message translate>\n The password of the user that you are currently logged in with.\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n title=\"{{ 'Confirm' | translate }}\"\n class=\"btn btn-primary\"\n [attr.data-cy]=\"'confirm-current-password-confirm-button'\"\n type=\"submit\"\n [disabled]=\"!confirmForm.form.valid\"\n >\n {{ 'Confirm' | translate }}\n </button>\n </div>\n </form>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19501
- }
19502
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CurrentPasswordModalComponent, decorators: [{
19503
- type: Component,
19504
- args: [{ selector: 'c8y-current-password-modal', standalone: true, imports: [
19505
- ModalComponent,
19506
- FormsModule$1,
19507
- FormGroupComponent,
19508
- C8yTranslateDirective,
19509
- RequiredInputPlaceholderDirective,
19510
- MessagesComponent,
19511
- MessageDirective,
19512
- C8yTranslatePipe
19513
- ], template: "<c8y-modal [customFooter]=\"true\" [title]=\"'Confirm your current password' | translate\" #modal>\n <form #confirmForm=\"ngForm\" (ngSubmit)=\"confirmForm.form.valid && passwordConfirm()\">\n <div class=\"d-block p-24\">\n <c8y-form-group [hasWarning]=\"true\">\n <label translate for=\"currentPassword\">Enter your password</label>\n <input\n id=\"currentPassword\"\n [(ngModel)]=\"password\"\n type=\"password\"\n name=\"password\"\n class=\"form-control\"\n required\n />\n <c8y-messages>\n <c8y-message translate>\n The password of the user that you are currently logged in with.\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n title=\"{{ 'Confirm' | translate }}\"\n class=\"btn btn-primary\"\n [attr.data-cy]=\"'confirm-current-password-confirm-button'\"\n type=\"submit\"\n [disabled]=\"!confirmForm.form.valid\"\n >\n {{ 'Confirm' | translate }}\n </button>\n </div>\n </form>\n</c8y-modal>\n" }]
19514
- }], ctorParameters: () => [{ type: i1$7.BsModalRef }], propDecorators: { currentPasswordEmitter: [{
19515
- type: Output
19516
- }] } });
19517
-
19518
- class PasswordService {
19519
- constructor(modalService) {
19520
- this.modalService = modalService;
19521
- this.DEFAULT_PASSWORD_MIN_LENGTH = 8;
19522
- this.GREEN = {
19523
- colorName: 'green',
19524
- color: 'rgb(0, 128, 0)',
19525
- description: gettext$1('strong'),
19526
- passwordStrength: PasswordStrength.GREEN
19527
- };
19528
- this.YELLOW = {
19529
- colorName: 'yellow',
19530
- color: 'rgb(255, 204, 51)',
19531
- description: gettext$1('medium'),
19532
- passwordStrength: PasswordStrength.YELLOW
19533
- };
19534
- this.RED = {
19535
- colorName: 'red',
19536
- color: 'rgb(170, 0, 51)',
19537
- description: gettext$1('weak'),
19538
- passwordStrength: PasswordStrength.RED
19539
- };
19540
- }
19541
- confirmPassword() {
19542
- const modalInstance = this.modalService.show(PasswordConfirmModalComponent, {
19543
- ariaDescribedby: 'modal-body',
19544
- ariaLabelledBy: 'modal-title'
19545
- });
19546
- const passwordConfirmedEmitter = modalInstance.content.passwordConfirmedEmitter;
19547
- return passwordConfirmedEmitter.pipe(take(1));
19560
+ /**
19561
+ * Checks if password strength is enforced for system
19562
+ * by retrieving value of `enforceStrength` property from loginOptions response
19563
+ * @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
19564
+ * If false, it takes value from memory,
19565
+ * if true, it refresh the app state value and then retrives data.
19566
+ * @return boolean value, true if enforced, false otherwise.
19567
+ */
19568
+ async getEnforcePasswordStrength(refresh) {
19569
+ const loginOption = await this.getBasicAuthLoginOption(refresh);
19570
+ const enforcePasswordStrength = loginOption?.enforceStrength;
19571
+ if (typeof enforcePasswordStrength === 'string') {
19572
+ this.passwordStrengthSetting.enforcePasswordStrength =
19573
+ enforcePasswordStrength === 'true' ? true : false;
19574
+ }
19575
+ else {
19576
+ this.passwordStrengthSetting.enforcePasswordStrength = !!enforcePasswordStrength;
19577
+ }
19578
+ return this.passwordStrengthSetting.enforcePasswordStrength;
19548
19579
  }
19549
19580
  /**
19550
- * Returns an observable with the password provided by user:
19551
- * - `string` when user provided a value
19552
- * - `null` when user cancelled the modal
19581
+ * Checks if password strength is enforced for particular tenant
19582
+ * by retrieving value of `strengthValidity` property from loginOptions response
19583
+ * @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
19584
+ * If false, it takes value from memory,
19585
+ * if true, it refresh the app state value and then retrives data.
19586
+ * @return boolean value, true if enforced, false otherwise.
19553
19587
  */
19554
- currentPassword() {
19555
- const modalInstance = this.modalService.show(CurrentPasswordModalComponent, {
19556
- class: 'modal-sm',
19557
- ariaDescribedby: 'modal-body',
19558
- ariaLabelledBy: 'modal-title'
19559
- });
19560
- const currentPasswordEmitter = modalInstance.content.currentPasswordEmitter;
19561
- return currentPasswordEmitter.pipe(take(1));
19562
- }
19563
- hasLowerCase(password) {
19564
- return password.search(/[a-z]/) !== -1;
19565
- }
19566
- hasUpperCase(password) {
19567
- return password.search(/[A-Z]/) !== -1;
19568
- }
19569
- hasNumbers(password) {
19570
- return password.search(/[0-9]/) !== -1;
19571
- }
19572
- hasSpecialChars(password) {
19573
- return password.search(/[^0-9a-zA-Z]+/) !== -1;
19574
- }
19575
- getStrengthColor(password) {
19576
- const passwordStrength = filter$2([
19577
- this.hasLowerCase(password),
19578
- this.hasUpperCase(password),
19579
- this.hasNumbers(password),
19580
- this.hasSpecialChars(password)
19581
- ]).length;
19582
- if (passwordStrength > 3) {
19583
- return this.GREEN;
19584
- }
19585
- else if (passwordStrength >= 3) {
19586
- return this.YELLOW;
19588
+ async getPasswordStrengthValidity(refresh = false) {
19589
+ const loginOption = await this.getBasicAuthLoginOption(refresh);
19590
+ const strengthValidity = loginOption?.strengthValidity;
19591
+ if (typeof strengthValidity === 'string') {
19592
+ this.passwordStrengthSetting.passwordStrengthValidity =
19593
+ strengthValidity === 'true' ? true : false;
19587
19594
  }
19588
19595
  else {
19589
- return this.RED;
19596
+ this.passwordStrengthSetting.passwordStrengthValidity = !!strengthValidity;
19590
19597
  }
19598
+ return this.passwordStrengthSetting.passwordStrengthValidity;
19591
19599
  }
19592
- getDefaultPasswordMinLength() {
19593
- return this.DEFAULT_PASSWORD_MIN_LENGTH;
19600
+ /**
19601
+ * Function determines if enforced strength checks should be enabled for current tenant
19602
+ * based on properties retrieved from loginOptions
19603
+ * @param options object containing specific options:
19604
+ * - {refresh: true} - refreshes values of app state and returns fresh values as result of call
19605
+ * @return boolean value, true if strength is enforced for tenant, false otherwise.
19606
+ */
19607
+ async getPasswordStrengthEnforced(options) {
19608
+ const refresh = options && options.refresh;
19609
+ return Promise.all([
19610
+ this.getEnforcePasswordStrength(refresh),
19611
+ this.getPasswordStrengthValidity(refresh)
19612
+ ]).then(values => {
19613
+ const [enforcePasswordStrength, passwordStrengthValidity] = values;
19614
+ return enforcePasswordStrength || passwordStrengthValidity;
19615
+ });
19594
19616
  }
19595
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, deps: [{ token: i1$7.BsModalService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19596
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, providedIn: 'root' }); }
19617
+ async getBasicAuthLoginOption(refresh) {
19618
+ if (refresh) {
19619
+ await this.ui.refreshLoginOptions();
19620
+ }
19621
+ const loginOptions = this.ui.state.loginOptions || [];
19622
+ const basicAuthLoginOption = loginOptions.find(({ type }) => type === 'BASIC');
19623
+ return basicAuthLoginOption;
19624
+ }
19625
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, deps: [{ token: AppStateService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19626
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, providedIn: 'root' }); }
19597
19627
  }
19598
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordService, decorators: [{
19628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, decorators: [{
19599
19629
  type: Injectable,
19600
- args: [{ providedIn: 'root' }]
19601
- }], ctorParameters: () => [{ type: i1$7.BsModalService }] });
19630
+ args: [{
19631
+ providedIn: 'root'
19632
+ }]
19633
+ }], ctorParameters: () => [{ type: AppStateService }] });
19602
19634
 
19603
- class TotpSetupComponent {
19604
- get qrCodeImage() {
19605
- return this.totpSecret ? this.totpSecret.secretQrUrl : '';
19606
- }
19607
- get secret() {
19608
- return this.totpSecret ? this.totpSecret.rawSecret : '';
19609
- }
19610
- get qrData() {
19611
- // TODO: waiting for BE:, now we need extract it from secretQrUrl
19612
- // https://cumulocity.atlassian.net/browse/MTM-36387
19613
- // return this.totpSecret ? this.totpSecret.qrData : '';
19614
- const url = new URL(this.qrCodeImage);
19615
- const otpAuth = url.searchParams.get('chl') || url.searchParams.get('data');
19616
- if (!otpAuth) {
19617
- this.alert.danger(gettext$1('Failed to generate a QR code.'));
19618
- return '';
19635
+ class PasswordCheckListComponent {
19636
+ set password(password) {
19637
+ if (typeof password === 'string') {
19638
+ this.onPasswordChange(password);
19619
19639
  }
19620
- return decodeURIComponent(otpAuth);
19621
19640
  }
19622
- constructor(user, alert) {
19623
- this.user = user;
19624
- this.alert = alert;
19641
+ constructor(passwordStrength, passwordStrengthChecker, passwordService) {
19642
+ this.passwordStrength = passwordStrength;
19643
+ this.passwordStrengthChecker = passwordStrengthChecker;
19644
+ this.passwordService = passwordService;
19645
+ this.strengthEnforced = false;
19646
+ this.onRequirementsFulfilled = new EventEmitter();
19647
+ this.minGreenLength = 8;
19648
+ this.enhancedStrengthCheckList = [
19649
+ {
19650
+ label: gettext$1('Include lowercase characters (for example, abcdef)'),
19651
+ check: this.passwordStrengthChecker.hasLowerCase,
19652
+ icon: '',
19653
+ contextualColor: '',
19654
+ textColor: ''
19655
+ },
19656
+ {
19657
+ label: gettext$1('Include uppercase characters (for example, ABCDEF)'),
19658
+ check: this.passwordStrengthChecker.hasUpperCase,
19659
+ icon: '',
19660
+ contextualColor: '',
19661
+ textColor: ''
19662
+ },
19663
+ {
19664
+ label: gettext$1('Include numbers (for example, 123456)'),
19665
+ check: this.passwordStrengthChecker.hasNumbers,
19666
+ icon: '',
19667
+ contextualColor: '',
19668
+ textColor: ''
19669
+ },
19670
+ {
19671
+ label: gettext$1('Include symbols (for example, !@#$%^)'),
19672
+ check: this.passwordStrengthChecker.hasSpecialChars,
19673
+ icon: '',
19674
+ contextualColor: '',
19675
+ textColor: ''
19676
+ }
19677
+ ];
19678
+ this.basicChecklist = [
19679
+ {
19680
+ label: gettext$1('Must have at least {{length}} characters'),
19681
+ check: password => password.length >= this.minGreenLength,
19682
+ icon: '',
19683
+ contextualColor: '',
19684
+ textColor: ''
19685
+ }
19686
+ ];
19687
+ this.combinedChecklist = [];
19625
19688
  }
19626
19689
  async ngOnInit() {
19627
- try {
19628
- const { data } = await this.user.generateTotpSecret();
19629
- this.totpSecret = data;
19630
- }
19631
- catch (ex) {
19632
- this.alert.addServerFailure(ex);
19690
+ this.minGreenLength = await this.passwordStrength.getGreenMinLength();
19691
+ if (!this.minGreenLength) {
19692
+ this.minGreenLength = this.passwordService.getDefaultPasswordMinLength();
19633
19693
  }
19694
+ this.onPasswordChange('');
19634
19695
  }
19635
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpSetupComponent, deps: [{ token: i1.UserService }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19636
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TotpSetupComponent, isStandalone: true, selector: "c8y-totp-setup", ngImport: i0, template: "<div class=\"text-center\">\n <p\n class=\"m-24 m-t-16 m-b-0\"\n translate\n >\n Scan this QR code with your smartphone using the authenticator application.\n </p>\n\n <div class=\"d-flex j-c-center\">\n <qrcode\n [width]=\"180\"\n *ngIf=\"totpSecret\"\n [qrdata]=\"qrData\"\n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'svg'\"\n ></qrcode>\n </div>\n <p class=\"text-center text-muted\">\n {{ secret }}\n </p>\n</div>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QRCodeComponent, selector: "qrcode", inputs: ["allowEmptyString", "colorDark", "colorLight", "cssClass", "elementType", "errorCorrectionLevel", "imageSrc", "imageHeight", "imageWidth", "margin", "qrdata", "scale", "version", "width", "alt", "ariaLabel", "title"], outputs: ["qrCodeURL"] }] }); }
19696
+ get translateParams() {
19697
+ return {
19698
+ length: this.minGreenLength
19699
+ };
19700
+ }
19701
+ checkRequirement(requirement, password) {
19702
+ const checked = requirement.check(password);
19703
+ assign(requirement, {
19704
+ icon: checked ? 'check-circle' : 'radio-button-unchecked',
19705
+ contextualColor: checked ? 'text-success' : 'text-muted',
19706
+ textColor: checked ? '' : 'text-muted'
19707
+ });
19708
+ return requirement;
19709
+ }
19710
+ onPasswordChange(password) {
19711
+ this.basicChecklist.forEach(requirement => {
19712
+ this.checkRequirement(requirement, password);
19713
+ });
19714
+ this.enhancedStrengthCheckList.forEach(requirement => {
19715
+ this.checkRequirement(requirement, password);
19716
+ });
19717
+ this.combinedChecklist = [...this.basicChecklist, ...this.enhancedStrengthCheckList];
19718
+ this.onRequirementsFulfilled.emit(this.isPasswordValid());
19719
+ }
19720
+ isPasswordValid() {
19721
+ const checklist = this.strengthEnforced ? this.combinedChecklist : this.basicChecklist;
19722
+ return checklist.every(requirement => requirement.icon !== 'radio-button-unchecked');
19723
+ }
19724
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordCheckListComponent, deps: [{ token: PasswordStrengthService }, { token: PasswordService }, { token: PasswordService }], target: i0.ɵɵFactoryTarget.Component }); }
19725
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordCheckListComponent, isStandalone: true, selector: "c8y-password-check-list", inputs: { strengthEnforced: "strengthEnforced", password: "password" }, outputs: { onRequirementsFulfilled: "onRequirementsFulfilled" }, ngImport: i0, template: "<div *ngIf=\"strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of combinedChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n\n<div *ngIf=\"!strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of basicChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n\n <div class=\"m-b-8\">\n {{ 'We recommend you to meet these conditions for a stronger password:' | translate }}\n </div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of enhancedStrengthCheckList\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19637
19726
  }
19638
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpSetupComponent, decorators: [{
19727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordCheckListComponent, decorators: [{
19639
19728
  type: Component,
19640
- args: [{ selector: 'c8y-totp-setup', standalone: true, imports: [C8yTranslateDirective, NgIf, QRCodeComponent], template: "<div class=\"text-center\">\n <p\n class=\"m-24 m-t-16 m-b-0\"\n translate\n >\n Scan this QR code with your smartphone using the authenticator application.\n </p>\n\n <div class=\"d-flex j-c-center\">\n <qrcode\n [width]=\"180\"\n *ngIf=\"totpSecret\"\n [qrdata]=\"qrData\"\n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'svg'\"\n ></qrcode>\n </div>\n <p class=\"text-center text-muted\">\n {{ secret }}\n </p>\n</div>\n" }]
19641
- }], ctorParameters: () => [{ type: i1.UserService }, { type: AlertService }] });
19729
+ args: [{ selector: 'c8y-password-check-list', standalone: true, imports: [NgIf, NgFor, IconDirective, C8yTranslateDirective, C8yTranslatePipe], template: "<div *ngIf=\"strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of combinedChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n\n<div *ngIf=\"!strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of basicChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n\n <div class=\"m-b-8\">\n {{ 'We recommend you to meet these conditions for a stronger password:' | translate }}\n </div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of enhancedStrengthCheckList\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n" }]
19730
+ }], ctorParameters: () => [{ type: PasswordStrengthService }, { type: PasswordService }, { type: PasswordService }], propDecorators: { strengthEnforced: [{
19731
+ type: Input
19732
+ }], password: [{
19733
+ type: Input,
19734
+ args: ['password']
19735
+ }], onRequirementsFulfilled: [{
19736
+ type: Output
19737
+ }] } });
19642
19738
 
19643
- class TotpChallengeComponent {
19644
- constructor(users, alert) {
19645
- this.users = users;
19646
- this.alert = alert;
19647
- /**
19648
- * Calls the verify endpoint if set to true (default true)
19649
- */
19650
- this.verify = true;
19651
- /**
19652
- * Emits the token on success.
19653
- */
19654
- this.onSuccess = new EventEmitter();
19655
- /**
19656
- * Emits if set up two-factor authentication is canceled.
19657
- */
19658
- this.totpUnconfirmedEmitter = new EventEmitter();
19659
- this.loading = false;
19660
- this.hasError = false;
19661
- this.isModal = false;
19662
- this.model = {
19663
- token: ''
19664
- };
19739
+ class NewPasswordComponent {
19740
+ set _newPasswordModel(ngModel) {
19741
+ if (ngModel) {
19742
+ this.newPasswordModel = ngModel;
19743
+ ngModel.control.addValidators(this.passwordChecklistValidator);
19744
+ }
19665
19745
  }
19666
- async verifyCode() {
19667
- try {
19668
- this.loading = true;
19669
- this.hasError = false;
19670
- if (this.verify) {
19671
- await this.users.verifyTotpCode(this.model.token);
19672
- }
19673
- this.onSuccess.emit(this.model.token);
19746
+ constructor(passwordStrength, cdRef, elementRef) {
19747
+ this.passwordStrength = passwordStrength;
19748
+ this.cdRef = cdRef;
19749
+ this.elementRef = elementRef;
19750
+ this.password = new EventEmitter();
19751
+ this.showChangePasswordButton = true;
19752
+ this.model = {};
19753
+ this.changePassword = false;
19754
+ this.passwordEnforced = false;
19755
+ this.passwordChecklistValidator = () => this.requirementsFulfilled ? null : { passwordStrengthChecklist: true };
19756
+ }
19757
+ ngOnInit() {
19758
+ this.loadPasswordStrengthSettings();
19759
+ this.passwordStrength.getGreenMinLength().then(value => {
19760
+ this.minlength = value;
19761
+ });
19762
+ }
19763
+ async ngOnChanges(changes) {
19764
+ if (changes.showChangePasswordButton) {
19765
+ this.changePassword = !this.showChangePasswordButton;
19674
19766
  }
19675
- catch (e) {
19676
- this.hasError = true;
19677
- this.alert.removeLastDanger();
19678
- this.loading = false;
19767
+ if (changes.requireStrongPassword?.previousValue !== changes.requireStrongPassword?.currentValue) {
19768
+ await this.loadPasswordStrengthSettings();
19679
19769
  }
19680
19770
  }
19681
- cancel() {
19682
- this.totpUnconfirmedEmitter.emit();
19771
+ newPasswordChanged() {
19772
+ this.password.emit({
19773
+ password: this.model.newPassword,
19774
+ passwordStrength: this.model.strength
19775
+ });
19683
19776
  }
19684
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpChallengeComponent, deps: [{ token: i1.UserService }, { token: AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
19685
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TotpChallengeComponent, isStandalone: true, selector: "c8y-totp-challenge", inputs: { verify: "verify", loading: "loading", hasError: "hasError", isModal: "isModal" }, outputs: { onSuccess: "onSuccess", totpUnconfirmedEmitter: "totpUnconfirmedEmitter" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<form #totpForm=\"ngForm\" class=\"loginForm\" (ngSubmit)=\"verifyCode()\" novalidate>\n <div class=\"d-block p-b-0\" [ngClass]=\"isModal ? 'p-24' : 'p-t-24'\">\n <c8y-form-group\n [hasError]=\"hasError\"\n [novalidation]=\"true\"\n [ngClass]=\"{ 'p-b-24': hasError }\"\n >\n <label translate for=\"totpToken\">Verification code</label>\n\n <input\n id=\"totpToken\"\n [(ngModel)]=\"model.token\"\n name=\"totpToken\"\n type=\"text\"\n autofocus\n autocapitalize=\"off\"\n autocorrect=\"off\"\n autocomplete=\"off\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} 624327\"\n required\n />\n\n <p id=\"helpinput\" *ngIf=\"!hasError\" class=\"help-block\" translate>\n In case of key loss, please contact your platform administrator.\n </p>\n </c8y-form-group>\n </div>\n <div [ngClass]=\"isModal ? 'modal-footer separator-top bg-level-0 sticky-bottom' : 'text-center'\">\n <button\n class=\"btn btn-default\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verify' | translate }}\"\n *ngIf=\"!loading\"\n [disabled]=\"!totpForm.form.valid\"\n >\n {{ 'Verify' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary btn-pending\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verifying\u2026' | translate }}\"\n *ngIf=\"loading\"\n >\n {{ 'Verifying\u2026' | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
19777
+ updateValidity(requirementsFulfilled) {
19778
+ this.requirementsFulfilled = requirementsFulfilled;
19779
+ this.cdRef.detectChanges();
19780
+ this.newPasswordModel.control.updateValueAndValidity();
19781
+ // There are two validators checking password validity, but we only want to show one of these errors at a time,
19782
+ // where checklist validator takes priority.
19783
+ if (!this.requirementsFulfilled) {
19784
+ delete this.newPasswordModel.control.errors['password'];
19785
+ }
19786
+ }
19787
+ async loadPasswordStrengthSettings() {
19788
+ if (this.requireStrongPassword) {
19789
+ this.passwordEnforced = this.requireStrongPassword;
19790
+ }
19791
+ else {
19792
+ const passwordStrengthSettings = await this.passwordStrength.getPasswordStrengthEnforced({
19793
+ refresh: true
19794
+ });
19795
+ this.passwordEnforced = passwordStrengthSettings;
19796
+ }
19797
+ }
19798
+ toggleChangePassword() {
19799
+ this.changePassword = !this.changePassword;
19800
+ if (!this.changePassword) {
19801
+ this.password.emit({});
19802
+ this.model = {};
19803
+ }
19804
+ }
19805
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NewPasswordComponent, deps: [{ token: PasswordStrengthService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
19806
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: NewPasswordComponent, isStandalone: true, selector: "c8y-new-password", inputs: { showChangePasswordButton: "showChangePasswordButton", requireStrongPassword: "requireStrongPassword" }, outputs: { password: "password" }, viewQueries: [{ propertyName: "_newPasswordModel", first: true, predicate: ["newPassword"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"showChangePasswordButton\" class=\"form-group\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n data-cy=\"c8y-new-password--change-button\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n</div>\n\n<div\n class=\"row content-flex-50\"\n *ngIf=\"changePassword\"\n>\n <div class=\"col-6\">\n <c8y-form-group>\n <label\n for=\"newPassword\"\n translate\n >\n Password\n </label>\n <c8y-password-input\n name=\"newPassword\"\n required\n [id]=\"'newPassword'\"\n #newPassword=\"ngModel\"\n [(ngModel)]=\"model.newPassword\"\n (change)=\"newPasswordChanged()\"\n (input)=\"newPasswordConfirm.control.updateValueAndValidity()\"\n c8yDefaultValidation=\"password\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"newConfirmPassword\"\n translate\n >\n Confirm password\n </label>\n <c8y-password-input\n name=\"newPasswordConfirm\"\n required\n [id]=\"'newConfirmPassword'\"\n #newPasswordConfirm=\"ngModel\"\n [(ngModel)]=\"model.newPasswordConfirm\"\n passwordConfirm=\"newPassword\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n </div>\n <div class=\"col-6\">\n <c8y-password-check-list\n [password]=\"model.newPassword\"\n [strengthEnforced]=\"passwordEnforced\"\n (onRequirementsFulfilled)=\"updateValidity($event)\"\n ></c8y-password-check-list>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: PasswordInputComponent, selector: "c8y-password-input", inputs: ["id", "autocomplete"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: DefaultValidationDirective, selector: "[c8yDefaultValidation]", inputs: ["c8yDefaultValidation"] }, { kind: "directive", type: PasswordConfirm, selector: "[passwordConfirm]" }, { kind: "component", type: PasswordCheckListComponent, selector: "c8y-password-check-list", inputs: ["strengthEnforced", "password"], outputs: ["onRequirementsFulfilled"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
19686
19807
  }
19687
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TotpChallengeComponent, decorators: [{
19808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NewPasswordComponent, decorators: [{
19688
19809
  type: Component,
19689
- args: [{ selector: 'c8y-totp-challenge', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
19690
- FormsModule$1,
19691
- NgClass,
19810
+ args: [{ selector: 'c8y-new-password', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
19811
+ NgIf,
19692
19812
  FormGroupComponent,
19693
19813
  C8yTranslateDirective,
19694
- RequiredInputPlaceholderDirective,
19695
- NgIf,
19814
+ PasswordInputComponent,
19815
+ FormsModule$1,
19816
+ DefaultValidationDirective,
19817
+ PasswordConfirm,
19818
+ PasswordCheckListComponent,
19696
19819
  C8yTranslatePipe
19697
- ], template: "<form #totpForm=\"ngForm\" class=\"loginForm\" (ngSubmit)=\"verifyCode()\" novalidate>\n <div class=\"d-block p-b-0\" [ngClass]=\"isModal ? 'p-24' : 'p-t-24'\">\n <c8y-form-group\n [hasError]=\"hasError\"\n [novalidation]=\"true\"\n [ngClass]=\"{ 'p-b-24': hasError }\"\n >\n <label translate for=\"totpToken\">Verification code</label>\n\n <input\n id=\"totpToken\"\n [(ngModel)]=\"model.token\"\n name=\"totpToken\"\n type=\"text\"\n autofocus\n autocapitalize=\"off\"\n autocorrect=\"off\"\n autocomplete=\"off\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} 624327\"\n required\n />\n\n <p id=\"helpinput\" *ngIf=\"!hasError\" class=\"help-block\" translate>\n In case of key loss, please contact your platform administrator.\n </p>\n </c8y-form-group>\n </div>\n <div [ngClass]=\"isModal ? 'modal-footer separator-top bg-level-0 sticky-bottom' : 'text-center'\">\n <button\n class=\"btn btn-default\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verify' | translate }}\"\n *ngIf=\"!loading\"\n [disabled]=\"!totpForm.form.valid\"\n >\n {{ 'Verify' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary btn-pending\"\n [ngClass]=\"{ 'btn-lg': !isModal }\"\n type=\"submit\"\n title=\"{{ 'Verifying\u2026' | translate }}\"\n *ngIf=\"loading\"\n >\n {{ 'Verifying\u2026' | translate }}\n </button>\n </div>\n</form>\n" }]
19698
- }], ctorParameters: () => [{ type: i1.UserService }, { type: AlertService }], propDecorators: { verify: [{
19699
- type: Input
19700
- }], onSuccess: [{
19701
- type: Output
19702
- }], totpUnconfirmedEmitter: [{
19820
+ ], template: "<div *ngIf=\"showChangePasswordButton\" class=\"form-group\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n data-cy=\"c8y-new-password--change-button\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n</div>\n\n<div\n class=\"row content-flex-50\"\n *ngIf=\"changePassword\"\n>\n <div class=\"col-6\">\n <c8y-form-group>\n <label\n for=\"newPassword\"\n translate\n >\n Password\n </label>\n <c8y-password-input\n name=\"newPassword\"\n required\n [id]=\"'newPassword'\"\n #newPassword=\"ngModel\"\n [(ngModel)]=\"model.newPassword\"\n (change)=\"newPasswordChanged()\"\n (input)=\"newPasswordConfirm.control.updateValueAndValidity()\"\n c8yDefaultValidation=\"password\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"newConfirmPassword\"\n translate\n >\n Confirm password\n </label>\n <c8y-password-input\n name=\"newPasswordConfirm\"\n required\n [id]=\"'newConfirmPassword'\"\n #newPasswordConfirm=\"ngModel\"\n [(ngModel)]=\"model.newPasswordConfirm\"\n passwordConfirm=\"newPassword\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n </div>\n <div class=\"col-6\">\n <c8y-password-check-list\n [password]=\"model.newPassword\"\n [strengthEnforced]=\"passwordEnforced\"\n (onRequirementsFulfilled)=\"updateValidity($event)\"\n ></c8y-password-check-list>\n </div>\n</div>\n" }]
19821
+ }], ctorParameters: () => [{ type: PasswordStrengthService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { password: [{
19703
19822
  type: Output
19704
- }], loading: [{
19705
- type: Input
19706
- }], hasError: [{
19823
+ }], showChangePasswordButton: [{
19707
19824
  type: Input
19708
- }], isModal: [{
19825
+ }], requireStrongPassword: [{
19709
19826
  type: Input
19710
- }], modal: [{
19827
+ }], _newPasswordModel: [{
19711
19828
  type: ViewChild,
19712
- args: ['modal', { static: false }]
19829
+ args: ['newPassword']
19713
19830
  }] } });
19714
19831
 
19715
- class UserTotpSetupComponent {
19716
- constructor(user, modalService, modal, authService) {
19717
- this.user = user;
19718
- this.modalService = modalService;
19719
- this.modal = modal;
19720
- this.authService = authService;
19721
- }
19722
- async totpSetupVerified() {
19723
- await this.user.activateTotp();
19724
- this.modal.hide();
19725
- try {
19726
- await this.modalService.acknowledge(gettext$1('Logout required'), gettext$1('You must log out in order to apply your changes'), Status.WARNING, gettext$1('Log out'));
19727
- await this.authService.logout();
19728
- }
19729
- catch (ex) {
19730
- // intended empty
19832
+ class UserEditComponent {
19833
+ set user(u) {
19834
+ if (u) {
19835
+ this._user = clone(u);
19836
+ this.userIsExternal = u.customProperties.userOrigin === 'OAUTH2';
19837
+ this.isPhoneRequired = this.isPhoneRequired && u.twoFactorAuthenticationEnabled;
19731
19838
  }
19732
19839
  }
19733
- close() {
19734
- this.modal.hide();
19735
- }
19736
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserTotpSetupComponent, deps: [{ token: i1.UserService }, { token: ModalService }, { token: i1$7.BsModalRef }, { token: SimplifiedAuthService }], target: i0.ɵɵFactoryTarget.Component }); }
19737
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserTotpSetupComponent, isStandalone: true, selector: "c8y-user-totp-setup", ngImport: i0, template: "<c8y-modal\n [title]=\"'Set up two-factor authentication' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span class=\"dlt-c8y-icon-phonelink-lock\"></span>\n </ng-container>\n\n <c8y-totp-setup></c8y-totp-setup>\n <c8y-totp-challenge [isModal]=\"true\" (onSuccess)=\"totpSetupVerified()\" (totpUnconfirmedEmitter)=\"close()\"></c8y-totp-challenge>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: TotpSetupComponent, selector: "c8y-totp-setup" }, { kind: "component", type: TotpChallengeComponent, selector: "c8y-totp-challenge", inputs: ["verify", "loading", "hasError", "isModal"], outputs: ["onSuccess", "totpUnconfirmedEmitter"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19738
- }
19739
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserTotpSetupComponent, decorators: [{
19740
- type: Component,
19741
- args: [{ selector: 'c8y-user-totp-setup', standalone: true, imports: [ModalComponent, TotpSetupComponent, TotpChallengeComponent, C8yTranslatePipe], template: "<c8y-modal\n [title]=\"'Set up two-factor authentication' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span class=\"dlt-c8y-icon-phonelink-lock\"></span>\n </ng-container>\n\n <c8y-totp-setup></c8y-totp-setup>\n <c8y-totp-challenge [isModal]=\"true\" (onSuccess)=\"totpSetupVerified()\" (totpUnconfirmedEmitter)=\"close()\"></c8y-totp-challenge>\n</c8y-modal>\n" }]
19742
- }], ctorParameters: () => [{ type: i1.UserService }, { type: ModalService }, { type: i1$7.BsModalRef }, { type: SimplifiedAuthService }] });
19743
-
19744
- /**
19745
- * The component is used to display a password control that includes the functionality of hiding and showing the input value.
19746
- */
19747
- class PasswordInputComponent {
19748
- constructor() {
19749
- /**
19750
- * Id of input
19751
- */
19752
- this.id = '';
19753
- /**
19754
- * Value for autocomplete attribute of input
19755
- */
19756
- this.autocomplete = 'off';
19757
- this.value = '';
19758
- this.disabled = false;
19759
- this.type = 'password';
19760
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
19761
- this.onChange = value => { };
19762
- // eslint-disable-next-line @typescript-eslint/no-empty-function
19763
- this.onTouched = () => { };
19840
+ get user() {
19841
+ return this._user;
19764
19842
  }
19765
- writeValue(value) {
19766
- this.value = value ? value : '';
19843
+ constructor(state, translate, bsModalService, alert, userService, tenantLoginOptionsService, tenantService) {
19844
+ this.state = state;
19845
+ this.translate = translate;
19846
+ this.bsModalService = bsModalService;
19847
+ this.alert = alert;
19848
+ this.userService = userService;
19849
+ this.tenantLoginOptionsService = tenantLoginOptionsService;
19850
+ this.tenantService = tenantService;
19851
+ this.loading = false;
19852
+ this.showProductExperienceOptions = false;
19853
+ this.isUsageTrackingEnabled = true;
19854
+ this.isUserEngagementPreferenceEnabled = true;
19855
+ this.focusOnNewPassword = false;
19856
+ this.onUser = new EventEmitter();
19857
+ this.onUsageTrackingChange = new EventEmitter();
19858
+ this.onUserEngagementPreferenceChange = new EventEmitter();
19859
+ this.onCancel = new EventEmitter();
19860
+ this.newPasswordComponent = viewChild(NewPasswordComponent);
19861
+ this.userHasActiveTotp = false;
19862
+ this.userCanSetupTotp = false;
19863
+ this.isPhoneRequired = false;
19864
+ effect(() => {
19865
+ if (this.focusOnNewPassword && this.newPasswordComponent()) {
19866
+ this.newPasswordComponent().toggleChangePassword();
19867
+ setTimeout(() => {
19868
+ this.newPasswordComponent().elementRef.nativeElement.scrollIntoView({
19869
+ behavior: 'smooth',
19870
+ block: 'center'
19871
+ });
19872
+ }, 100);
19873
+ }
19874
+ });
19767
19875
  }
19768
- registerOnChange(fn) {
19769
- this.onChange = fn;
19876
+ async ngOnInit() {
19877
+ const currentTenant = (await this.tenantService.current()).data;
19878
+ const { enabledOnSystemLevel, enabledOnTenantLevel } = await this.tenantService.getTfaSettings(currentTenant);
19879
+ this.isTfaEnabled = enabledOnSystemLevel || enabledOnTenantLevel;
19880
+ await this.initializeTotpSettings();
19881
+ if (this.user.twoFactorAuthenticationEnabled && !this.userCanSetupTotp) {
19882
+ this.isPhoneRequired = true;
19883
+ }
19770
19884
  }
19771
- registerOnTouched(fn) {
19772
- this.onTouched = fn;
19885
+ setupTotp() {
19886
+ this.bsModalService.show(UserTotpSetupComponent, {
19887
+ class: 'modal-sm',
19888
+ ariaDescribedby: 'modal-body',
19889
+ ariaLabelledBy: 'modal-title'
19890
+ });
19891
+ this.cancel(); // to close the user edit modal and prevent console errors on logout
19773
19892
  }
19774
- setDisabledState(isDisabled) {
19775
- this.disabled = isDisabled;
19893
+ cancel() {
19894
+ this.onCancel.emit();
19776
19895
  }
19777
- onInput($event) {
19778
- this.value = $event.currentTarget.value;
19779
- this.onChange(this.value);
19896
+ async save() {
19897
+ if (this.loading) {
19898
+ return;
19899
+ }
19900
+ if (this.showProductExperienceOptions) {
19901
+ this.onUsageTrackingChange.emit(this.isUsageTrackingEnabled);
19902
+ /**
19903
+ * Emits a user engagement preference change event.
19904
+ * If usage tracking is disabled, it emits `false`. Otherwise, it emits the current state of the user engagement preference.
19905
+ */
19906
+ this.onUserEngagementPreferenceChange.emit(this.isUsageTrackingEnabled === false ? false : this.isUserEngagementPreferenceEnabled);
19907
+ }
19908
+ this.onUser.emit(this._user);
19780
19909
  }
19781
- onFocusOut() {
19782
- this.onTouched();
19910
+ onNewPasswordChanged(newPassword) {
19911
+ this._user.password = newPassword.password;
19783
19912
  }
19784
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
19785
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordInputComponent, isStandalone: true, selector: "c8y-password-input", inputs: { id: "id", autocomplete: "autocomplete" }, providers: [
19786
- {
19787
- provide: NG_VALUE_ACCESSOR,
19788
- useExisting: forwardRef(() => PasswordInputComponent),
19789
- multi: true
19913
+ async initializeTotpSettings() {
19914
+ try {
19915
+ this.userCanSetupTotp = await this.canUserSetupTotp();
19916
+ if (this.userCanSetupTotp) {
19917
+ const { data: totpActivity } = await this.userService.getActivityTotp();
19918
+ this.userHasActiveTotp = totpActivity.isActive;
19790
19919
  }
19791
- ], ngImport: i0, template: "<div class=\"input-group input-group-lg input-group-password\">\n <input\n class=\"form-control input-lg\"\n [type]=\"type\"\n [value]=\"value\"\n [id]=\"id\"\n (input)=\"onInput($event)\"\n (focusout)=\"onFocusOut()\"\n [disabled]=\"disabled\"\n [autocomplete]=\"autocomplete\"\n />\n <span class=\"input-group-btn\">\n <button\n *ngIf=\"type === 'password'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Show password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'text'\"\n >\n <i class=\"dlt-c8y-icon-eye\"></i>\n </button>\n <button\n *ngIf=\"type === 'text'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Hide password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'password'\"\n >\n <i class=\"dlt-c8y-icon-eye-slash\"></i>\n </button>\n </span>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
19920
+ }
19921
+ catch (ex) {
19922
+ this.alert.removeLastDanger();
19923
+ }
19924
+ }
19925
+ async canUserSetupTotp() {
19926
+ // we don't check for tenant options here due to permission restrictions on that end-point
19927
+ const loginOptions = (await this.tenantLoginOptionsService.listForCurrentTenant()).data;
19928
+ return loginOptions.some(({ tfaStrategy = '' }) => tfaStrategy.toLowerCase() === 'totp');
19929
+ }
19930
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditComponent, deps: [{ token: AppStateService }, { token: TranslateService }, { token: i1$7.BsModalService }, { token: AlertService }, { token: i1.UserService }, { token: i1.TenantLoginOptionsService }, { token: i1.TenantService }], target: i0.ɵɵFactoryTarget.Component }); }
19931
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: UserEditComponent, isStandalone: true, selector: "c8y-user-edit", inputs: { loading: "loading", user: "user", showProductExperienceOptions: "showProductExperienceOptions", isUsageTrackingEnabled: "isUsageTrackingEnabled", isUserEngagementPreferenceEnabled: "isUserEngagementPreferenceEnabled", focusOnNewPassword: "focusOnNewPassword" }, outputs: { onUser: "onUser", onUsageTrackingChange: "onUsageTrackingChange", onUserEngagementPreferenceChange: "onUserEngagementPreferenceChange", onCancel: "onCancel" }, viewQueries: [{ propertyName: "newPasswordComponent", first: true, predicate: NewPasswordComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form #userForm=\"ngForm\" (ngSubmit)=\"userForm.form.valid && save()\">\n <div class=\"d-block p-24 p-b-0\">\n <div class=\"alert alert-warning\" role=\"alert\" *ngIf=\"userIsExternal\" translate>\n Some of the user settings are not editable here because they are managed via your\n authorization server.\n </div>\n <c8y-form-group>\n <label translate for=\"userName\">Username</label>\n <input\n id=\"userName\"\n class=\"form-control\"\n [(ngModel)]=\"user.userName\"\n name=\"userName\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [disabled]=\"user.id\"\n c8yDefaultValidation=\"user\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate for=\"displayName\">Login alias</label>\n <input\n id=\"displayName\"\n class=\"form-control\"\n [(ngModel)]=\"user.displayName\"\n name=\"displayName\"\n autocomplete=\"off\"\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n [disabled]=\"userIsExternal\"\n c8yDefaultValidation=\"loginAlias\"\n />\n </c8y-form-group>\n\n <c8y-form-group [hasWarning]=\"!user.email\">\n <label translate for=\"userEmail\">Email</label>\n <input\n id=\"userEmail\"\n class=\"form-control\"\n type=\"email\"\n name=\"email\"\n [maxlength]=\"254\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [(ngModel)]=\"user.email\"\n email\n [required]=\"true\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userFirstName\">First name</label>\n <input\n id=\"userFirstName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Joe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"firstName\"\n [(ngModel)]=\"user.firstName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userLastName\">Last name</label>\n <input\n id=\"userLastName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Doe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"lastName\"\n [(ngModel)]=\"user.lastName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n </div>\n\n <c8y-form-group>\n <label translate for=\"userTelephone\">Telephone</label>\n <input\n id=\"userTelephone\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"phone\"\n maxlength=\"254\"\n [(ngModel)]=\"user.phone\"\n placeholder=\"{{ 'e.g. +49 9 876 543 210`LOCALIZE`' | translate }}\"\n c8yPhoneValidation\n [required]=\"isPhoneRequired\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"p-t-16 separator-top\" *ngIf=\"showProductExperienceOptions\">\n <label translate>Product experience</label>\n <label class=\"c8y-switch\" for=\"productUsageTracking\">\n <input\n id=\"productUsageTracking\"\n name=\"productUsageTracking\"\n type=\"checkbox\"\n [(ngModel)]=\"isUsageTrackingEnabled\"\n />\n <span></span>\n {{ 'Enable personalized product experience tracking' | translate }}\n </label>\n <ng-container *ngIf=\"isUsageTrackingEnabled\">\n <label class=\"c8y-switch m-l-0\" for=\"userEngagementPreference\">\n <input\n id=\"userEngagementPreference\"\n name=\"userEngagementPreference\"\n type=\"checkbox\"\n [(ngModel)]=\"isUserEngagementPreferenceEnabled\"\n />\n <span></span>\n {{ 'Enable in-product information & communication' | translate }}\n </label>\n </ng-container>\n </c8y-form-group>\n\n <div class=\"form-group p-t-16 separator-top\" *ngIf=\"!userIsExternal\">\n <label class=\"control-label\">{{ 'Login options' | translate }}</label>\n <c8y-new-password (password)=\"onNewPasswordChanged($event)\"></c8y-new-password>\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Set up two-factor authentication' | translate }}\"\n (click)=\"setupTotp()\"\n *ngIf=\"userCanSetupTotp && !userHasActiveTotp && isTfaEnabled\"\n >\n {{ 'Set up two-factor authentication' | translate }}\n </button>\n </div>\n\n <c8y-form-group *ngIf=\"!!(state.state$ | async).newsletter\">\n <label translate>Newsletter</label>\n <label\n title=\"{{ 'Send me information about outages, maintenance or updates.' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n name=\"newsletter\"\n [(ngModel)]=\"user.newsletter\"\n [disabled]=\"userIsExternal\"\n />\n <span></span>\n <span>\n {{ 'Send me information about outages, maintenance or updates.' | translate }}\n </span>\n </label>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Save' | translate }}\"\n [disabled]=\"!userForm.form.valid || userForm.form.pristine || loading\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$8.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: DefaultValidationDirective, selector: "[c8yDefaultValidation]", inputs: ["c8yDefaultValidation"] }, { kind: "directive", type: PhoneValidationDirective, selector: "[c8yPhoneValidation]" }, { kind: "component", type: NewPasswordComponent, selector: "c8y-new-password", inputs: ["showChangePasswordButton", "requireStrongPassword"], outputs: ["password"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
19792
19932
  }
19793
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordInputComponent, decorators: [{
19933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditComponent, decorators: [{
19794
19934
  type: Component,
19795
- args: [{ selector: 'c8y-password-input', providers: [
19796
- {
19797
- provide: NG_VALUE_ACCESSOR,
19798
- useExisting: forwardRef(() => PasswordInputComponent),
19799
- multi: true
19800
- }
19801
- ], standalone: true, imports: [NgIf, C8yTranslatePipe], template: "<div class=\"input-group input-group-lg input-group-password\">\n <input\n class=\"form-control input-lg\"\n [type]=\"type\"\n [value]=\"value\"\n [id]=\"id\"\n (input)=\"onInput($event)\"\n (focusout)=\"onFocusOut()\"\n [disabled]=\"disabled\"\n [autocomplete]=\"autocomplete\"\n />\n <span class=\"input-group-btn\">\n <button\n *ngIf=\"type === 'password'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Show password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'text'\"\n >\n <i class=\"dlt-c8y-icon-eye\"></i>\n </button>\n <button\n *ngIf=\"type === 'text'\"\n class=\"btn btn-clean\"\n title=\"{{ 'Hide password' | translate }}\"\n type=\"button\"\n (click)=\"type = 'password'\"\n >\n <i class=\"dlt-c8y-icon-eye-slash\"></i>\n </button>\n </span>\n</div>\n" }]
19802
- }], propDecorators: { id: [{
19935
+ args: [{ selector: 'c8y-user-edit', standalone: true, imports: [
19936
+ FormsModule$1,
19937
+ NgIf,
19938
+ C8yTranslateDirective,
19939
+ FormGroupComponent,
19940
+ RequiredInputPlaceholderDirective,
19941
+ DefaultValidationDirective,
19942
+ PhoneValidationDirective,
19943
+ NewPasswordComponent,
19944
+ C8yTranslatePipe,
19945
+ AsyncPipe
19946
+ ], template: "<form #userForm=\"ngForm\" (ngSubmit)=\"userForm.form.valid && save()\">\n <div class=\"d-block p-24 p-b-0\">\n <div class=\"alert alert-warning\" role=\"alert\" *ngIf=\"userIsExternal\" translate>\n Some of the user settings are not editable here because they are managed via your\n authorization server.\n </div>\n <c8y-form-group>\n <label translate for=\"userName\">Username</label>\n <input\n id=\"userName\"\n class=\"form-control\"\n [(ngModel)]=\"user.userName\"\n name=\"userName\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [disabled]=\"user.id\"\n c8yDefaultValidation=\"user\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate for=\"displayName\">Login alias</label>\n <input\n id=\"displayName\"\n class=\"form-control\"\n [(ngModel)]=\"user.displayName\"\n name=\"displayName\"\n autocomplete=\"off\"\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n [disabled]=\"userIsExternal\"\n c8yDefaultValidation=\"loginAlias\"\n />\n </c8y-form-group>\n\n <c8y-form-group [hasWarning]=\"!user.email\">\n <label translate for=\"userEmail\">Email</label>\n <input\n id=\"userEmail\"\n class=\"form-control\"\n type=\"email\"\n name=\"email\"\n [maxlength]=\"254\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [(ngModel)]=\"user.email\"\n email\n [required]=\"true\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userFirstName\">First name</label>\n <input\n id=\"userFirstName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Joe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"firstName\"\n [(ngModel)]=\"user.firstName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userLastName\">Last name</label>\n <input\n id=\"userLastName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Doe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"lastName\"\n [(ngModel)]=\"user.lastName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n </div>\n\n <c8y-form-group>\n <label translate for=\"userTelephone\">Telephone</label>\n <input\n id=\"userTelephone\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"phone\"\n maxlength=\"254\"\n [(ngModel)]=\"user.phone\"\n placeholder=\"{{ 'e.g. +49 9 876 543 210`LOCALIZE`' | translate }}\"\n c8yPhoneValidation\n [required]=\"isPhoneRequired\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"p-t-16 separator-top\" *ngIf=\"showProductExperienceOptions\">\n <label translate>Product experience</label>\n <label class=\"c8y-switch\" for=\"productUsageTracking\">\n <input\n id=\"productUsageTracking\"\n name=\"productUsageTracking\"\n type=\"checkbox\"\n [(ngModel)]=\"isUsageTrackingEnabled\"\n />\n <span></span>\n {{ 'Enable personalized product experience tracking' | translate }}\n </label>\n <ng-container *ngIf=\"isUsageTrackingEnabled\">\n <label class=\"c8y-switch m-l-0\" for=\"userEngagementPreference\">\n <input\n id=\"userEngagementPreference\"\n name=\"userEngagementPreference\"\n type=\"checkbox\"\n [(ngModel)]=\"isUserEngagementPreferenceEnabled\"\n />\n <span></span>\n {{ 'Enable in-product information & communication' | translate }}\n </label>\n </ng-container>\n </c8y-form-group>\n\n <div class=\"form-group p-t-16 separator-top\" *ngIf=\"!userIsExternal\">\n <label class=\"control-label\">{{ 'Login options' | translate }}</label>\n <c8y-new-password (password)=\"onNewPasswordChanged($event)\"></c8y-new-password>\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Set up two-factor authentication' | translate }}\"\n (click)=\"setupTotp()\"\n *ngIf=\"userCanSetupTotp && !userHasActiveTotp && isTfaEnabled\"\n >\n {{ 'Set up two-factor authentication' | translate }}\n </button>\n </div>\n\n <c8y-form-group *ngIf=\"!!(state.state$ | async).newsletter\">\n <label translate>Newsletter</label>\n <label\n title=\"{{ 'Send me information about outages, maintenance or updates.' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n name=\"newsletter\"\n [(ngModel)]=\"user.newsletter\"\n [disabled]=\"userIsExternal\"\n />\n <span></span>\n <span>\n {{ 'Send me information about outages, maintenance or updates.' | translate }}\n </span>\n </label>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Save' | translate }}\"\n [disabled]=\"!userForm.form.valid || userForm.form.pristine || loading\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n" }]
19947
+ }], ctorParameters: () => [{ type: AppStateService }, { type: TranslateService }, { type: i1$7.BsModalService }, { type: AlertService }, { type: i1.UserService }, { type: i1.TenantLoginOptionsService }, { type: i1.TenantService }], propDecorators: { loading: [{
19803
19948
  type: Input
19804
- }], autocomplete: [{
19949
+ }], user: [{
19950
+ type: Input
19951
+ }], showProductExperienceOptions: [{
19952
+ type: Input
19953
+ }], isUsageTrackingEnabled: [{
19954
+ type: Input
19955
+ }], isUserEngagementPreferenceEnabled: [{
19956
+ type: Input
19957
+ }], focusOnNewPassword: [{
19805
19958
  type: Input
19959
+ }], onUser: [{
19960
+ type: Output
19961
+ }], onUsageTrackingChange: [{
19962
+ type: Output
19963
+ }], onUserEngagementPreferenceChange: [{
19964
+ type: Output
19965
+ }], onCancel: [{
19966
+ type: Output
19806
19967
  }] } });
19807
19968
 
19808
- class PasswordConfirm {
19809
- constructor(passwordConfirm) {
19810
- this.passwordConfirm = passwordConfirm;
19811
- }
19812
- validate(abControl) {
19813
- const value = abControl.value;
19814
- const controlToCompareWith = abControl.root.get(this.passwordConfirm);
19815
- if (controlToCompareWith && value !== controlToCompareWith.value) {
19816
- return { passwordConfirm: true };
19817
- }
19818
- return null;
19819
- }
19820
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirm, deps: [{ token: 'passwordConfirm', attribute: true }], target: i0.ɵɵFactoryTarget.Directive }); }
19821
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.14", type: PasswordConfirm, isStandalone: true, selector: "[passwordConfirm]", providers: [
19822
- {
19823
- provide: NG_VALIDATORS,
19824
- useExisting: PasswordConfirm,
19825
- multi: true
19826
- }
19827
- ], ngImport: i0 }); }
19828
- }
19829
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordConfirm, decorators: [{
19830
- type: Directive,
19831
- args: [{
19832
- selector: '[passwordConfirm]',
19833
- providers: [
19834
- {
19835
- provide: NG_VALIDATORS,
19836
- useExisting: PasswordConfirm,
19837
- multi: true
19838
- }
19839
- ],
19840
- standalone: true
19841
- }]
19842
- }], ctorParameters: () => [{ type: undefined, decorators: [{
19843
- type: Attribute,
19844
- args: ['passwordConfirm']
19845
- }] }] });
19846
-
19847
- class PasswordStrengthService {
19848
- constructor(ui) {
19969
+ class UserEditModalComponent {
19970
+ constructor(modal, user, ui, auth, client, alert, userPreferences, c8yModalService, gainsightService, cookieBannerService, passwordService, userEngagementsService) {
19971
+ this.modal = modal;
19972
+ this.user = user;
19849
19973
  this.ui = ui;
19850
- this.GREEN_MIN_LENGTH_DEFAULT = 8;
19851
- this.passwordStrengthSetting = {
19852
- enforcePasswordStrength: false,
19853
- greenMinLength: this.GREEN_MIN_LENGTH_DEFAULT,
19854
- passwordStrengthValidity: false
19855
- };
19974
+ this.auth = auth;
19975
+ this.client = client;
19976
+ this.alert = alert;
19977
+ this.userPreferences = userPreferences;
19978
+ this.c8yModalService = c8yModalService;
19979
+ this.gainsightService = gainsightService;
19980
+ this.cookieBannerService = cookieBannerService;
19981
+ this.passwordService = passwordService;
19982
+ this.userEngagementsService = userEngagementsService;
19983
+ this.loading = false;
19984
+ this.showProductExperienceOptions = false;
19985
+ this.passwordChange = false;
19856
19986
  }
19857
- /**
19858
- * Gets the minimal number of characters that a password should have to be considered a “green” strong one.
19859
- * @return The min length for password or default value.
19860
- */
19861
- async getGreenMinLength() {
19862
- const { greenMinLength } = (await this.getBasicAuthLoginOption()) || { greenMinLength: null };
19863
- this.passwordStrengthSetting.greenMinLength = greenMinLength || this.GREEN_MIN_LENGTH_DEFAULT;
19864
- return this.passwordStrengthSetting.greenMinLength;
19987
+ async ngOnInit() {
19988
+ this.updateUserInAppState();
19989
+ await this.setInitialProductExperienceOptions();
19865
19990
  }
19866
19991
  /**
19867
- * Checks if password strength is enforced for system
19868
- * by retrieving value of `enforceStrength` property from loginOptions response
19869
- * @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
19870
- * If false, it takes value from memory,
19871
- * if true, it refresh the app state value and then retrives data.
19872
- * @return boolean value, true if enforced, false otherwise.
19992
+ * Initializes product experience options for the user.
19993
+ *
19994
+ * This function performs the following operations:
19995
+ * - Determines if the user has the permission to edit product experience options.
19996
+ * - If the user has the permission and functional cookies are enabled:
19997
+ * - Checks whether personalized product experience tracking is active.
19998
+ * - Checks whether in-product information and communication is active.
19873
19999
  */
19874
- async getEnforcePasswordStrength(refresh) {
19875
- const loginOption = await this.getBasicAuthLoginOption(refresh);
19876
- const enforcePasswordStrength = loginOption?.enforceStrength;
19877
- if (typeof enforcePasswordStrength === 'string') {
19878
- this.passwordStrengthSetting.enforcePasswordStrength =
19879
- enforcePasswordStrength === 'true' ? true : false;
20000
+ async setInitialProductExperienceOptions() {
20001
+ this.showProductExperienceOptions =
20002
+ await this.gainsightService.canEditProductExperienceSettings();
20003
+ if (this.showProductExperienceOptions && this.cookieBannerService.isFunctionalCookieEnabled()) {
20004
+ // Enable personalized product experience tracking option
20005
+ this.currentUsageTrackingState =
20006
+ !(await this.gainsightService.isGainsightPreferenceDisabledInUserPreferences(this.gainsightService.USER_PREFERENCES_GAINSIGHT_KEY));
20007
+ // Enable in-product information & communication option
20008
+ this.currentUserEngagementPreferenceInitialState =
20009
+ this.userEngagementsService.userEngagementsEnabled$.value;
19880
20010
  }
19881
- else {
19882
- this.passwordStrengthSetting.enforcePasswordStrength = !!enforcePasswordStrength;
20011
+ }
20012
+ async onDismiss() {
20013
+ this.modal.hide();
20014
+ }
20015
+ onUsageTrackingChange(isEnabled) {
20016
+ this.usageTrackingState = isEnabled;
20017
+ }
20018
+ onUserEngagementPreferenceChange(isEnabled) {
20019
+ this.userEngagementPreferenceNewState = isEnabled;
20020
+ }
20021
+ async updateAndClose(user) {
20022
+ this.loading = true;
20023
+ try {
20024
+ const passwordChanged = Boolean(user.password);
20025
+ const isExternalUser = user.customProperties.userOrigin === 'OAUTH2';
20026
+ if (!isExternalUser && passwordChanged) {
20027
+ const currentPassword = await this.passwordService.currentPassword().toPromise();
20028
+ if (!currentPassword) {
20029
+ return;
20030
+ }
20031
+ await this.user.changeCurrentUserPassword(user.password, currentPassword);
20032
+ this.updateCredentials(user.password);
20033
+ }
20034
+ if (user.customProperties.userOrigin !== 'OAUTH2') {
20035
+ await this.user.updateCurrent(omit(user, 'password'));
20036
+ await this.updateUserInAppState();
20037
+ }
20038
+ await this.updateProductExperienceOptions();
20039
+ this.modal.hide();
20040
+ this.alert.success(gettext$1('User saved.'));
20041
+ }
20042
+ catch (e) {
20043
+ if (e) {
20044
+ this.alert.addServerFailure(e);
20045
+ }
20046
+ }
20047
+ finally {
20048
+ this.loading = false;
19883
20049
  }
19884
- return this.passwordStrengthSetting.enforcePasswordStrength;
19885
20050
  }
19886
- /**
19887
- * Checks if password strength is enforced for particular tenant
19888
- * by retrieving value of `strengthValidity` property from loginOptions response
19889
- * @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
19890
- * If false, it takes value from memory,
19891
- * if true, it refresh the app state value and then retrives data.
19892
- * @return boolean value, true if enforced, false otherwise.
19893
- */
19894
- async getPasswordStrengthValidity(refresh = false) {
19895
- const loginOption = await this.getBasicAuthLoginOption(refresh);
19896
- const strengthValidity = loginOption?.strengthValidity;
19897
- if (typeof strengthValidity === 'string') {
19898
- this.passwordStrengthSetting.passwordStrengthValidity =
19899
- strengthValidity === 'true' ? true : false;
20051
+ async gainsightTrackingAppReload() {
20052
+ try {
20053
+ await this.c8yModalService.confirm(gettext$1('Reload required'), gettext$1('To change the tracking option in the entire application, you need to reload the page. If you have any unsaved changes, you can reload later. How would you like to proceed?'), Status.WARNING, {
20054
+ ok: gettext$1('Reload now'),
20055
+ cancel: gettext$1('Reload later')
20056
+ });
20057
+ location.reload();
19900
20058
  }
19901
- else {
19902
- this.passwordStrengthSetting.passwordStrengthValidity = !!strengthValidity;
20059
+ catch (ex) {
20060
+ // do nothing
19903
20061
  }
19904
- return this.passwordStrengthSetting.passwordStrengthValidity;
20062
+ }
20063
+ async updateProductExperienceOptions() {
20064
+ this.updateUserEngagementsPreference();
20065
+ await this.updateTrackingOption();
19905
20066
  }
19906
20067
  /**
19907
- * Function determines if enforced strength checks should be enabled for current tenant
19908
- * based on properties retrieved from loginOptions
19909
- * @param options object containing specific options:
19910
- * - {refresh: true} - refreshes values of app state and returns fresh values as result of call
19911
- * @return boolean value, true if strength is enforced for tenant, false otherwise.
20068
+ * Updates the user engagement preference if it has changed from the initial state.
20069
+ * Calls the user engagements service to update the preference.
20070
+ *
20071
+ * The update only occurs if the current preference differs from the new state.
19912
20072
  */
19913
- async getPasswordStrengthEnforced(options) {
19914
- const refresh = options && options.refresh;
19915
- return Promise.all([
19916
- this.getEnforcePasswordStrength(refresh),
19917
- this.getPasswordStrengthValidity(refresh)
19918
- ]).then(values => {
19919
- const [enforcePasswordStrength, passwordStrengthValidity] = values;
19920
- return enforcePasswordStrength || passwordStrengthValidity;
19921
- });
20073
+ updateUserEngagementsPreference() {
20074
+ if (this.currentUserEngagementPreferenceInitialState !== this.userEngagementPreferenceNewState) {
20075
+ this.userEngagementsService.updateUserEngagementPreference(this.userEngagementPreferenceNewState);
20076
+ }
19922
20077
  }
19923
- async getBasicAuthLoginOption(refresh) {
19924
- if (refresh) {
19925
- await this.ui.refreshLoginOptions();
19926
- }
19927
- const loginOptions = this.ui.state.loginOptions || [];
19928
- const basicAuthLoginOption = loginOptions.find(({ type }) => type === 'BASIC');
19929
- return basicAuthLoginOption;
19930
- }
19931
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, deps: [{ token: AppStateService }], target: i0.ɵɵFactoryTarget.Injectable }); }
19932
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, providedIn: 'root' }); }
19933
- }
19934
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordStrengthService, decorators: [{
19935
- type: Injectable,
19936
- args: [{
19937
- providedIn: 'root'
19938
- }]
19939
- }], ctorParameters: () => [{ type: AppStateService }] });
19940
-
19941
- class PasswordCheckListComponent {
19942
- set password(password) {
19943
- if (typeof password === 'string') {
19944
- this.onPasswordChange(password);
20078
+ /**
20079
+ * Asynchronously updates the tracking option for user preferences.
20080
+ * If the current usage tracking state differs from the new state,
20081
+ * it updates the Gainsight preferences and sets a functional cookie
20082
+ * before triggering a reload of the application.
20083
+ */
20084
+ async updateTrackingOption() {
20085
+ if (this.currentUsageTrackingState !== this.usageTrackingState) {
20086
+ await this.userPreferences.set(this.gainsightService.USER_PREFERENCES_GAINSIGHT_KEY, this.usageTrackingState);
20087
+ this.gainsightService.setFunctionalCookie(this.usageTrackingState);
20088
+ await this.gainsightTrackingAppReload();
19945
20089
  }
19946
20090
  }
19947
- constructor(passwordStrength, passwordStrengthChecker, passwordService) {
19948
- this.passwordStrength = passwordStrength;
19949
- this.passwordStrengthChecker = passwordStrengthChecker;
19950
- this.passwordService = passwordService;
19951
- this.strengthEnforced = false;
19952
- this.onRequirementsFulfilled = new EventEmitter();
19953
- this.minGreenLength = 8;
19954
- this.enhancedStrengthCheckList = [
19955
- {
19956
- label: gettext$1('Include lowercase characters (for example, abcdef)'),
19957
- check: this.passwordStrengthChecker.hasLowerCase,
19958
- icon: '',
19959
- contextualColor: '',
19960
- textColor: ''
19961
- },
19962
- {
19963
- label: gettext$1('Include uppercase characters (for example, ABCDEF)'),
19964
- check: this.passwordStrengthChecker.hasUpperCase,
19965
- icon: '',
19966
- contextualColor: '',
19967
- textColor: ''
19968
- },
19969
- {
19970
- label: gettext$1('Include numbers (for example, 123456)'),
19971
- check: this.passwordStrengthChecker.hasNumbers,
19972
- icon: '',
19973
- contextualColor: '',
19974
- textColor: ''
19975
- },
19976
- {
19977
- label: gettext$1('Include symbols (for example, !@#$%^)'),
19978
- check: this.passwordStrengthChecker.hasSpecialChars,
19979
- icon: '',
19980
- contextualColor: '',
19981
- textColor: ''
19982
- }
19983
- ];
19984
- this.basicChecklist = [
19985
- {
19986
- label: gettext$1('Must have at least {{length}} characters'),
19987
- check: password => password.length >= this.minGreenLength,
19988
- icon: '',
19989
- contextualColor: '',
19990
- textColor: ''
19991
- }
19992
- ];
19993
- this.combinedChecklist = [];
19994
- }
19995
- async ngOnInit() {
19996
- this.minGreenLength = await this.passwordStrength.getGreenMinLength();
19997
- if (!this.minGreenLength) {
19998
- this.minGreenLength = this.passwordService.getDefaultPasswordMinLength();
19999
- }
20000
- this.onPasswordChange('');
20091
+ async updateUserInAppState() {
20092
+ const currentUserResult = await this.user.current();
20093
+ this.ui.currentUser.next(currentUserResult.data);
20001
20094
  }
20002
- get translateParams() {
20003
- return {
20004
- length: this.minGreenLength
20095
+ updateCredentials(password) {
20096
+ const newCredentials = {
20097
+ password,
20098
+ user: this.ui.currentUser.value.id,
20099
+ tenant: this.client.tenant
20005
20100
  };
20101
+ this.auth.updateCredentials(newCredentials);
20006
20102
  }
20007
- checkRequirement(requirement, password) {
20008
- const checked = requirement.check(password);
20009
- assign(requirement, {
20010
- icon: checked ? 'check-circle' : 'radio-button-unchecked',
20011
- contextualColor: checked ? 'text-success' : 'text-muted',
20012
- textColor: checked ? '' : 'text-muted'
20013
- });
20014
- return requirement;
20015
- }
20016
- onPasswordChange(password) {
20017
- this.basicChecklist.forEach(requirement => {
20018
- this.checkRequirement(requirement, password);
20019
- });
20020
- this.enhancedStrengthCheckList.forEach(requirement => {
20021
- this.checkRequirement(requirement, password);
20022
- });
20023
- this.combinedChecklist = [...this.basicChecklist, ...this.enhancedStrengthCheckList];
20024
- this.onRequirementsFulfilled.emit(this.isPasswordValid());
20025
- }
20026
- isPasswordValid() {
20027
- const checklist = this.strengthEnforced ? this.combinedChecklist : this.basicChecklist;
20028
- return checklist.every(requirement => requirement.icon !== 'radio-button-unchecked');
20029
- }
20030
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordCheckListComponent, deps: [{ token: PasswordStrengthService }, { token: PasswordService }, { token: PasswordService }], target: i0.ɵɵFactoryTarget.Component }); }
20031
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PasswordCheckListComponent, isStandalone: true, selector: "c8y-password-check-list", inputs: { strengthEnforced: "strengthEnforced", password: "password" }, outputs: { onRequirementsFulfilled: "onRequirementsFulfilled" }, ngImport: i0, template: "<div *ngIf=\"strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of combinedChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n\n<div *ngIf=\"!strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of basicChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n\n <div class=\"m-b-8\">\n {{ 'We recommend you to meet these conditions for a stronger password:' | translate }}\n </div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of enhancedStrengthCheckList\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
20103
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditModalComponent, deps: [{ token: i1$7.BsModalRef }, { token: i1.UserService }, { token: AppStateService }, { token: i1.BasicAuth }, { token: i1.FetchClient }, { token: AlertService }, { token: UserPreferencesService }, { token: ModalService }, { token: GainsightService }, { token: CookieBannerService }, { token: PasswordService }, { token: UserEngagementsService }], target: i0.ɵɵFactoryTarget.Component }); }
20104
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserEditModalComponent, isStandalone: true, selector: "c8y-user-edit-modal", ngImport: i0, template: "<c8y-modal\n [title]=\"'Edit user' | translate\"\n [customFooter]=\"true\"\n (onDismiss)=\"onDismiss()\"\n>\n <c8y-user-edit\n [user]=\"ui.currentUser | async\"\n [loading]=\"loading\"\n [isUsageTrackingEnabled]=\"currentUsageTrackingState\"\n [isUserEngagementPreferenceEnabled]=\"currentUserEngagementPreferenceInitialState\"\n [showProductExperienceOptions]=\"showProductExperienceOptions\"\n [focusOnNewPassword]=\"passwordChange\"\n (onUsageTrackingChange)=\"onUsageTrackingChange($event)\"\n (onUserEngagementPreferenceChange)=\"onUserEngagementPreferenceChange($event)\"\n (onUser)=\"updateAndClose($event)\"\n (onCancel)=\"onDismiss()\"\n ></c8y-user-edit>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: UserEditComponent, selector: "c8y-user-edit", inputs: ["loading", "user", "showProductExperienceOptions", "isUsageTrackingEnabled", "isUserEngagementPreferenceEnabled", "focusOnNewPassword"], outputs: ["onUser", "onUsageTrackingChange", "onUserEngagementPreferenceChange", "onCancel"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
20032
20105
  }
20033
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordCheckListComponent, decorators: [{
20106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditModalComponent, decorators: [{
20034
20107
  type: Component,
20035
- args: [{ selector: 'c8y-password-check-list', standalone: true, imports: [NgIf, NgFor, IconDirective, C8yTranslateDirective, C8yTranslatePipe], template: "<div *ngIf=\"strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of combinedChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n\n<div *ngIf=\"!strengthEnforced\">\n <div class=\"m-b-8\">{{ 'Password must meet the requirements below:' | translate }}</div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of basicChecklist\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n\n <div class=\"m-b-8\">\n {{ 'We recommend you to meet these conditions for a stronger password:' | translate }}\n </div>\n <ul class=\"list-unstyled\">\n <li\n class=\"small d-flex\"\n *ngFor=\"let requirement of enhancedStrengthCheckList\"\n >\n <i\n class=\"{{ requirement.contextualColor }}\"\n [c8yIcon]=\"requirement.icon\"\n ></i>\n <span\n class=\"m-l-4 small {{ requirement.textColor }}\"\n [translate]=\"requirement.label\"\n [translateParams]=\"this.translateParams\"\n ></span>\n </li>\n </ul>\n</div>\n" }]
20036
- }], ctorParameters: () => [{ type: PasswordStrengthService }, { type: PasswordService }, { type: PasswordService }], propDecorators: { strengthEnforced: [{
20037
- type: Input
20038
- }], password: [{
20039
- type: Input,
20040
- args: ['password']
20041
- }], onRequirementsFulfilled: [{
20042
- type: Output
20043
- }] } });
20108
+ args: [{ selector: 'c8y-user-edit-modal', standalone: true, imports: [ModalComponent, UserEditComponent, C8yTranslatePipe, AsyncPipe], template: "<c8y-modal\n [title]=\"'Edit user' | translate\"\n [customFooter]=\"true\"\n (onDismiss)=\"onDismiss()\"\n>\n <c8y-user-edit\n [user]=\"ui.currentUser | async\"\n [loading]=\"loading\"\n [isUsageTrackingEnabled]=\"currentUsageTrackingState\"\n [isUserEngagementPreferenceEnabled]=\"currentUserEngagementPreferenceInitialState\"\n [showProductExperienceOptions]=\"showProductExperienceOptions\"\n [focusOnNewPassword]=\"passwordChange\"\n (onUsageTrackingChange)=\"onUsageTrackingChange($event)\"\n (onUserEngagementPreferenceChange)=\"onUserEngagementPreferenceChange($event)\"\n (onUser)=\"updateAndClose($event)\"\n (onCancel)=\"onDismiss()\"\n ></c8y-user-edit>\n</c8y-modal>\n" }]
20109
+ }], ctorParameters: () => [{ type: i1$7.BsModalRef }, { type: i1.UserService }, { type: AppStateService }, { type: i1.BasicAuth }, { type: i1.FetchClient }, { type: AlertService }, { type: UserPreferencesService }, { type: ModalService }, { type: GainsightService }, { type: CookieBannerService }, { type: PasswordService }, { type: UserEngagementsService }] });
20044
20110
 
20045
- class NewPasswordComponent {
20046
- set _newPasswordModel(ngModel) {
20047
- if (ngModel) {
20048
- this.newPasswordModel = ngModel;
20049
- ngModel.control.addValidators(this.passwordChecklistValidator);
20050
- }
20051
- }
20052
- constructor(passwordStrength, cdRef) {
20053
- this.passwordStrength = passwordStrength;
20054
- this.cdRef = cdRef;
20055
- this.password = new EventEmitter();
20056
- this.showChangePasswordButton = true;
20057
- this.model = {};
20058
- this.changePassword = false;
20059
- this.passwordEnforced = false;
20060
- this.passwordChecklistValidator = () => this.requirementsFulfilled ? null : { passwordStrengthChecklist: true };
20111
+ /**
20112
+ * Service to show a modal.
20113
+ */
20114
+ class ModalService {
20115
+ constructor(modalService, gainsightService) {
20116
+ this.modalService = modalService;
20117
+ this.gainsightService = gainsightService;
20061
20118
  }
20062
- ngOnInit() {
20063
- this.loadPasswordStrengthSettings();
20064
- this.passwordStrength.getGreenMinLength().then(value => {
20065
- this.minlength = value;
20119
+ /**
20120
+ * Shows a quick confirm message modal.
20121
+ * @param title The title of that modal.
20122
+ * @param body The text body to display.
20123
+ * @param status The status.
20124
+ * @param labels The labels to use. Default: { ok: 'Confirm', cancel: 'Cancel'}
20125
+ * @param confirmOptions Selection options to display as checkbox list.
20126
+ * @param productExperienceEvent Additional data to attach to custom product experience events.
20127
+ */
20128
+ async confirm(title, body, status = Status.INFO, labels = {}, confirmOptions = {}, productExperienceEvent = { eventName: 'confirmModal' }) {
20129
+ const modalLabels = {
20130
+ ok: labels.ok || gettext$1('Confirm'),
20131
+ cancel: labels.cancel || gettext$1('Cancel')
20132
+ };
20133
+ const modalRef = this.modalService.show(ConfirmModalComponent, {
20134
+ initialState: { title, body, labels: modalLabels, status, confirmOptions },
20135
+ ariaDescribedby: 'modal-body',
20136
+ ariaLabelledBy: 'modal-title',
20137
+ ignoreBackdropClick: true
20066
20138
  });
20067
- }
20068
- async ngOnChanges(changes) {
20069
- if (changes.showChangePasswordButton) {
20070
- this.changePassword = !this.showChangePasswordButton;
20071
- }
20072
- if (changes.requireStrongPassword?.previousValue !== changes.requireStrongPassword?.currentValue) {
20073
- await this.loadPasswordStrengthSettings();
20139
+ if (productExperienceEvent) {
20140
+ productExperienceEvent.data = { ...productExperienceEvent.data, title };
20074
20141
  }
20142
+ this.triggerEvent(modalRef.content.result, modalLabels, productExperienceEvent);
20143
+ return await modalRef.content.result;
20075
20144
  }
20076
- newPasswordChanged() {
20077
- this.password.emit({
20078
- password: this.model.newPassword,
20079
- passwordStrength: this.model.strength
20145
+ /**
20146
+ * Shows a quick acknowledge message modal.
20147
+ * @param title The title of that modal.
20148
+ * @param body The text body to display.
20149
+ * @param status The status.
20150
+ * @param acknowledgeLabel The label to use.
20151
+ * @param productExperienceEvent Additional data to attach to custom product experience events.
20152
+ */
20153
+ async acknowledge(title, body, status = Status.INFO, acknowledgeLabel = gettext$1('Confirm'), productExperienceEvent = { eventName: 'confirmModal' }) {
20154
+ const labels = { ok: acknowledgeLabel, cancel: null };
20155
+ const modalRef = this.modalService.show(ConfirmModalComponent, {
20156
+ initialState: { title, body, labels, status },
20157
+ ariaDescribedby: 'modal-body',
20158
+ ariaLabelledBy: 'modal-title',
20159
+ ignoreBackdropClick: true
20080
20160
  });
20081
- }
20082
- updateValidity(requirementsFulfilled) {
20083
- this.requirementsFulfilled = requirementsFulfilled;
20084
- this.cdRef.detectChanges();
20085
- this.newPasswordModel.control.updateValueAndValidity();
20086
- // There are two validators checking password validity, but we only want to show one of these errors at a time,
20087
- // where checklist validator takes priority.
20088
- if (!this.requirementsFulfilled) {
20089
- delete this.newPasswordModel.control.errors['password'];
20090
- }
20091
- }
20092
- async loadPasswordStrengthSettings() {
20093
- if (this.requireStrongPassword) {
20094
- this.passwordEnforced = this.requireStrongPassword;
20095
- }
20096
- else {
20097
- const passwordStrengthSettings = await this.passwordStrength.getPasswordStrengthEnforced({
20098
- refresh: true
20099
- });
20100
- this.passwordEnforced = passwordStrengthSettings;
20101
- }
20102
- }
20103
- toggleChangePassword() {
20104
- this.changePassword = !this.changePassword;
20105
- if (!this.changePassword) {
20106
- this.password.emit({});
20107
- this.model = {};
20108
- }
20109
- }
20110
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NewPasswordComponent, deps: [{ token: PasswordStrengthService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
20111
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: NewPasswordComponent, isStandalone: true, selector: "c8y-new-password", inputs: { showChangePasswordButton: "showChangePasswordButton", requireStrongPassword: "requireStrongPassword" }, outputs: { password: "password" }, viewQueries: [{ propertyName: "_newPasswordModel", first: true, predicate: ["newPassword"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"showChangePasswordButton\" class=\"form-group\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n data-cy=\"c8y-new-password--change-button\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n</div>\n\n<div\n class=\"row content-flex-50\"\n *ngIf=\"changePassword\"\n>\n <div class=\"col-6\">\n <c8y-form-group>\n <label\n for=\"newPassword\"\n translate\n >\n Password\n </label>\n <c8y-password-input\n name=\"newPassword\"\n required\n [id]=\"'newPassword'\"\n #newPassword=\"ngModel\"\n [(ngModel)]=\"model.newPassword\"\n (change)=\"newPasswordChanged()\"\n (input)=\"newPasswordConfirm.control.updateValueAndValidity()\"\n c8yDefaultValidation=\"password\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"newConfirmPassword\"\n translate\n >\n Confirm password\n </label>\n <c8y-password-input\n name=\"newPasswordConfirm\"\n required\n [id]=\"'newConfirmPassword'\"\n #newPasswordConfirm=\"ngModel\"\n [(ngModel)]=\"model.newPasswordConfirm\"\n passwordConfirm=\"newPassword\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n </div>\n <div class=\"col-6\">\n <c8y-password-check-list\n [password]=\"model.newPassword\"\n [strengthEnforced]=\"passwordEnforced\"\n (onRequirementsFulfilled)=\"updateValidity($event)\"\n ></c8y-password-check-list>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: PasswordInputComponent, selector: "c8y-password-input", inputs: ["id", "autocomplete"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: DefaultValidationDirective, selector: "[c8yDefaultValidation]", inputs: ["c8yDefaultValidation"] }, { kind: "directive", type: PasswordConfirm, selector: "[passwordConfirm]" }, { kind: "component", type: PasswordCheckListComponent, selector: "c8y-password-check-list", inputs: ["strengthEnforced", "password"], outputs: ["onRequirementsFulfilled"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
20112
- }
20113
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NewPasswordComponent, decorators: [{
20114
- type: Component,
20115
- args: [{ selector: 'c8y-new-password', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
20116
- NgIf,
20117
- FormGroupComponent,
20118
- C8yTranslateDirective,
20119
- PasswordInputComponent,
20120
- FormsModule$1,
20121
- DefaultValidationDirective,
20122
- PasswordConfirm,
20123
- PasswordCheckListComponent,
20124
- C8yTranslatePipe
20125
- ], template: "<div *ngIf=\"showChangePasswordButton\" class=\"form-group\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n data-cy=\"c8y-new-password--change-button\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n</div>\n\n<div\n class=\"row content-flex-50\"\n *ngIf=\"changePassword\"\n>\n <div class=\"col-6\">\n <c8y-form-group>\n <label\n for=\"newPassword\"\n translate\n >\n Password\n </label>\n <c8y-password-input\n name=\"newPassword\"\n required\n [id]=\"'newPassword'\"\n #newPassword=\"ngModel\"\n [(ngModel)]=\"model.newPassword\"\n (change)=\"newPasswordChanged()\"\n (input)=\"newPasswordConfirm.control.updateValueAndValidity()\"\n c8yDefaultValidation=\"password\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"newConfirmPassword\"\n translate\n >\n Confirm password\n </label>\n <c8y-password-input\n name=\"newPasswordConfirm\"\n required\n [id]=\"'newConfirmPassword'\"\n #newPasswordConfirm=\"ngModel\"\n [(ngModel)]=\"model.newPasswordConfirm\"\n passwordConfirm=\"newPassword\"\n [autocomplete]=\"'new-password'\"\n ></c8y-password-input>\n </c8y-form-group>\n </div>\n <div class=\"col-6\">\n <c8y-password-check-list\n [password]=\"model.newPassword\"\n [strengthEnforced]=\"passwordEnforced\"\n (onRequirementsFulfilled)=\"updateValidity($event)\"\n ></c8y-password-check-list>\n </div>\n</div>\n" }]
20126
- }], ctorParameters: () => [{ type: PasswordStrengthService }, { type: i0.ChangeDetectorRef }], propDecorators: { password: [{
20127
- type: Output
20128
- }], showChangePasswordButton: [{
20129
- type: Input
20130
- }], requireStrongPassword: [{
20131
- type: Input
20132
- }], _newPasswordModel: [{
20133
- type: ViewChild,
20134
- args: ['newPassword']
20135
- }] } });
20136
-
20137
- class UserEditComponent {
20138
- set user(u) {
20139
- if (u) {
20140
- this._user = clone(u);
20141
- this.userIsExternal = u.customProperties.userOrigin === 'OAUTH2';
20142
- this.isPhoneRequired = this.isPhoneRequired && u.twoFactorAuthenticationEnabled;
20161
+ if (productExperienceEvent) {
20162
+ productExperienceEvent.data = { ...productExperienceEvent.data, title };
20143
20163
  }
20164
+ this.triggerEvent(modalRef.content.result, labels, productExperienceEvent);
20165
+ return await modalRef.content.result;
20144
20166
  }
20145
- get user() {
20146
- return this._user;
20147
- }
20148
- constructor(state, translate, bsModalService, alert, userService, tenantLoginOptionsService, tenantService) {
20149
- this.state = state;
20150
- this.translate = translate;
20151
- this.bsModalService = bsModalService;
20152
- this.alert = alert;
20153
- this.userService = userService;
20154
- this.tenantLoginOptionsService = tenantLoginOptionsService;
20155
- this.tenantService = tenantService;
20156
- this.loading = false;
20157
- this.showProductExperienceOptions = false;
20158
- this.isUsageTrackingEnabled = true;
20159
- this.isUserEngagementPreferenceEnabled = true;
20160
- this.onUser = new EventEmitter();
20161
- this.onUsageTrackingChange = new EventEmitter();
20162
- this.onUserEngagementPreferenceChange = new EventEmitter();
20163
- this.onCancel = new EventEmitter();
20164
- this.userHasActiveTotp = false;
20165
- this.userCanSetupTotp = false;
20166
- this.isPhoneRequired = false;
20167
- }
20168
- async ngOnInit() {
20169
- const currentTenant = (await this.tenantService.current()).data;
20170
- const { enabledOnSystemLevel, enabledOnTenantLevel } = await this.tenantService.getTfaSettings(currentTenant);
20171
- this.isTfaEnabled = enabledOnSystemLevel || enabledOnTenantLevel;
20172
- await this.initializeTotpSettings();
20173
- if (this.user.twoFactorAuthenticationEnabled && !this.userCanSetupTotp) {
20174
- this.isPhoneRequired = true;
20175
- }
20167
+ /**
20168
+ * Shows a quick logout confirmation modal.
20169
+ * @param body The text body to display. Default: 'You will be logged out to apply your changes. Do you want to proceed?'
20170
+ * @param status The status.
20171
+ * @param labels The labels to use. Default: { ok: 'Confirm and log out', cancel: 'Cancel' }
20172
+ */
20173
+ async confirmLogout(body, status = Status.WARNING, labels = {}) {
20174
+ const modalLabels = {
20175
+ ok: labels.ok || gettext$1('Confirm and log out'),
20176
+ cancel: labels.cancel || gettext$1('Cancel')
20177
+ };
20178
+ const modalBody = body || gettext$1('You must log out to apply your changes. Do you want to proceed?');
20179
+ return await this.confirm(gettext$1('Logout required'), modalBody, status, modalLabels);
20176
20180
  }
20177
- setupTotp() {
20178
- this.bsModalService.show(UserTotpSetupComponent, {
20179
- class: 'modal-sm',
20181
+ async changeCurrentUserPassword() {
20182
+ this.modalService.show(UserEditModalComponent, {
20183
+ initialState: { passwordChange: true },
20180
20184
  ariaDescribedby: 'modal-body',
20181
20185
  ariaLabelledBy: 'modal-title'
20182
20186
  });
20183
- this.cancel(); // to close the user edit modal and prevent console errors on logout
20184
- }
20185
- cancel() {
20186
- this.onCancel.emit();
20187
- }
20188
- async save() {
20189
- if (this.loading) {
20190
- return;
20191
- }
20192
- if (this.showProductExperienceOptions) {
20193
- this.onUsageTrackingChange.emit(this.isUsageTrackingEnabled);
20194
- /**
20195
- * Emits a user engagement preference change event.
20196
- * If usage tracking is disabled, it emits `false`. Otherwise, it emits the current state of the user engagement preference.
20197
- */
20198
- this.onUserEngagementPreferenceChange.emit(this.isUsageTrackingEnabled === false ? false : this.isUserEngagementPreferenceEnabled);
20199
- }
20200
- this.onUser.emit(this._user);
20201
20187
  }
20202
- onNewPasswordChanged(newPassword) {
20203
- this._user.password = newPassword.password;
20188
+ triggerEvent(result, labels, productExperienceEvent) {
20189
+ const data = { ...productExperienceEvent.data, url: window.location.href };
20190
+ result
20191
+ .then(() => {
20192
+ this.gainsightService.triggerEvent(productExperienceEvent.eventName, {
20193
+ ...data,
20194
+ result: labels.ok
20195
+ });
20196
+ })
20197
+ .catch(() => {
20198
+ this.gainsightService.triggerEvent(productExperienceEvent.eventName, {
20199
+ ...data,
20200
+ result: labels.cancel
20201
+ });
20202
+ });
20204
20203
  }
20205
- async initializeTotpSettings() {
20206
- try {
20207
- this.userCanSetupTotp = await this.canUserSetupTotp();
20208
- if (this.userCanSetupTotp) {
20209
- const { data: totpActivity } = await this.userService.getActivityTotp();
20210
- this.userHasActiveTotp = totpActivity.isActive;
20204
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, deps: [{ token: i1$7.BsModalService }, { token: GainsightService }], target: i0.ɵɵFactoryTarget.Injectable }); }
20205
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, providedIn: 'root' }); }
20206
+ }
20207
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ModalService, decorators: [{
20208
+ type: Injectable,
20209
+ args: [{
20210
+ providedIn: 'root'
20211
+ }]
20212
+ }], ctorParameters: () => [{ type: i1$7.BsModalService }, { type: GainsightService }] });
20213
+
20214
+ class ThemeSwitcherService {
20215
+ constructor(options) {
20216
+ this.options = options;
20217
+ this.darkThemeClass = `c8y-dark-theme`;
20218
+ this.themeOptions = [
20219
+ {
20220
+ label: gettext$1('Light'),
20221
+ value: 'light',
20222
+ icon: 'sun'
20223
+ },
20224
+ {
20225
+ label: gettext$1('Dark'),
20226
+ value: 'dark',
20227
+ icon: 'moon'
20228
+ },
20229
+ {
20230
+ label: gettext$1('System'),
20231
+ value: 'system',
20232
+ icon: 'imac-settings'
20211
20233
  }
20212
- }
20213
- catch (ex) {
20214
- this.alert.removeLastDanger();
20215
- }
20216
- }
20217
- async canUserSetupTotp() {
20218
- // we don't check for tenant options here due to permission restrictions on that end-point
20219
- const loginOptions = (await this.tenantLoginOptionsService.listForCurrentTenant()).data;
20220
- return loginOptions.some(({ tfaStrategy = '' }) => tfaStrategy.toLowerCase() === 'totp');
20234
+ ];
20235
+ this._userSelectedThemePreference$ = new BehaviorSubject(this.getCurrentThemePreference());
20236
+ this._temporaryThemePreference$ = new BehaviorSubject('none');
20237
+ this.userSelectedThemePreference$ = this._userSelectedThemePreference$.asObservable();
20238
+ const userSelectedTheme$ = this.userSelectedThemePreference$.pipe(switchMap(preference => {
20239
+ if (preference === 'system') {
20240
+ return this.getUsersSystemPreferenceForTheme$();
20241
+ }
20242
+ return of(preference);
20243
+ }));
20244
+ this.disableThemeSelection$ = this._temporaryThemePreference$.pipe(map(preference => preference !== 'none'));
20245
+ this.currentlyAppliedTheme$ = this._temporaryThemePreference$.pipe(switchMap(temporaryPreference => {
20246
+ if (temporaryPreference !== 'none') {
20247
+ return of(temporaryPreference);
20248
+ }
20249
+ return userSelectedTheme$;
20250
+ }));
20251
+ this.darkThemeAvailable$ = this.options.get$('darkThemeAvailable').pipe(map(value => !!value));
20221
20252
  }
20222
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditComponent, deps: [{ token: AppStateService }, { token: TranslateService }, { token: i1$7.BsModalService }, { token: AlertService }, { token: i1.UserService }, { token: i1.TenantLoginOptionsService }, { token: i1.TenantService }], target: i0.ɵɵFactoryTarget.Component }); }
20223
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserEditComponent, isStandalone: true, selector: "c8y-user-edit", inputs: { loading: "loading", user: "user", showProductExperienceOptions: "showProductExperienceOptions", isUsageTrackingEnabled: "isUsageTrackingEnabled", isUserEngagementPreferenceEnabled: "isUserEngagementPreferenceEnabled" }, outputs: { onUser: "onUser", onUsageTrackingChange: "onUsageTrackingChange", onUserEngagementPreferenceChange: "onUserEngagementPreferenceChange", onCancel: "onCancel" }, ngImport: i0, template: "<form #userForm=\"ngForm\" (ngSubmit)=\"userForm.form.valid && save()\">\n <div class=\"d-block p-24 p-b-0\">\n <div class=\"alert alert-warning\" role=\"alert\" *ngIf=\"userIsExternal\" translate>\n Some of the user settings are not editable here because they are managed via your\n authorization server.\n </div>\n <c8y-form-group>\n <label translate for=\"userName\">Username</label>\n <input\n id=\"userName\"\n class=\"form-control\"\n [(ngModel)]=\"user.userName\"\n name=\"userName\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [disabled]=\"user.id\"\n c8yDefaultValidation=\"user\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate for=\"displayName\">Login alias</label>\n <input\n id=\"displayName\"\n class=\"form-control\"\n [(ngModel)]=\"user.displayName\"\n name=\"displayName\"\n autocomplete=\"off\"\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n [disabled]=\"userIsExternal\"\n c8yDefaultValidation=\"loginAlias\"\n />\n </c8y-form-group>\n\n <c8y-form-group [hasWarning]=\"!user.email\">\n <label translate for=\"userEmail\">Email</label>\n <input\n id=\"userEmail\"\n class=\"form-control\"\n type=\"email\"\n name=\"email\"\n [maxlength]=\"254\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [(ngModel)]=\"user.email\"\n email\n [required]=\"true\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userFirstName\">First name</label>\n <input\n id=\"userFirstName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Joe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"firstName\"\n [(ngModel)]=\"user.firstName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userLastName\">Last name</label>\n <input\n id=\"userLastName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Doe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"lastName\"\n [(ngModel)]=\"user.lastName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n </div>\n\n <c8y-form-group>\n <label translate for=\"userTelephone\">Telephone</label>\n <input\n id=\"userTelephone\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"phone\"\n maxlength=\"254\"\n [(ngModel)]=\"user.phone\"\n placeholder=\"{{ 'e.g. +49 9 876 543 210`LOCALIZE`' | translate }}\"\n c8yPhoneValidation\n [required]=\"isPhoneRequired\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"p-t-16 separator-top\" *ngIf=\"showProductExperienceOptions\">\n <label translate>Product experience</label>\n <label class=\"c8y-switch\" for=\"productUsageTracking\">\n <input\n id=\"productUsageTracking\"\n name=\"productUsageTracking\"\n type=\"checkbox\"\n [(ngModel)]=\"isUsageTrackingEnabled\"\n />\n <span></span>\n {{ 'Enable personalized product experience tracking' | translate }}\n </label>\n <ng-container *ngIf=\"isUsageTrackingEnabled\">\n <label class=\"c8y-switch m-l-0\" for=\"userEngagementPreference\">\n <input\n id=\"userEngagementPreference\"\n name=\"userEngagementPreference\"\n type=\"checkbox\"\n [(ngModel)]=\"isUserEngagementPreferenceEnabled\"\n />\n <span></span>\n {{ 'Enable in-product information & communication' | translate }}\n </label>\n </ng-container>\n </c8y-form-group>\n\n <div class=\"form-group p-t-16 separator-top\" *ngIf=\"!userIsExternal\">\n <label class=\"control-label\">{{ 'Login options' | translate }}</label>\n <c8y-new-password (password)=\"onNewPasswordChanged($event)\"></c8y-new-password>\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Set up two-factor authentication' | translate }}\"\n (click)=\"setupTotp()\"\n *ngIf=\"userCanSetupTotp && !userHasActiveTotp && isTfaEnabled\"\n >\n {{ 'Set up two-factor authentication' | translate }}\n </button>\n </div>\n\n <c8y-form-group *ngIf=\"!!(state.state$ | async).newsletter\">\n <label translate>Newsletter</label>\n <label\n title=\"{{ 'Send me information about outages, maintenance or updates.' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n name=\"newsletter\"\n [(ngModel)]=\"user.newsletter\"\n [disabled]=\"userIsExternal\"\n />\n <span></span>\n <span>\n {{ 'Send me information about outages, maintenance or updates.' | translate }}\n </span>\n </label>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Save' | translate }}\"\n [disabled]=\"!userForm.form.valid || userForm.form.pristine || loading\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.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$8.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$8.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: DefaultValidationDirective, selector: "[c8yDefaultValidation]", inputs: ["c8yDefaultValidation"] }, { kind: "directive", type: PhoneValidationDirective, selector: "[c8yPhoneValidation]" }, { kind: "component", type: NewPasswordComponent, selector: "c8y-new-password", inputs: ["showChangePasswordButton", "requireStrongPassword"], outputs: ["password"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
20224
- }
20225
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditComponent, decorators: [{
20226
- type: Component,
20227
- args: [{ selector: 'c8y-user-edit', standalone: true, imports: [
20228
- FormsModule$1,
20229
- NgIf,
20230
- C8yTranslateDirective,
20231
- FormGroupComponent,
20232
- RequiredInputPlaceholderDirective,
20233
- DefaultValidationDirective,
20234
- PhoneValidationDirective,
20235
- NewPasswordComponent,
20236
- C8yTranslatePipe,
20237
- AsyncPipe
20238
- ], template: "<form #userForm=\"ngForm\" (ngSubmit)=\"userForm.form.valid && save()\">\n <div class=\"d-block p-24 p-b-0\">\n <div class=\"alert alert-warning\" role=\"alert\" *ngIf=\"userIsExternal\" translate>\n Some of the user settings are not editable here because they are managed via your\n authorization server.\n </div>\n <c8y-form-group>\n <label translate for=\"userName\">Username</label>\n <input\n id=\"userName\"\n class=\"form-control\"\n [(ngModel)]=\"user.userName\"\n name=\"userName\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [disabled]=\"user.id\"\n c8yDefaultValidation=\"user\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate for=\"displayName\">Login alias</label>\n <input\n id=\"displayName\"\n class=\"form-control\"\n [(ngModel)]=\"user.displayName\"\n name=\"displayName\"\n autocomplete=\"off\"\n maxlength=\"254\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n [disabled]=\"userIsExternal\"\n c8yDefaultValidation=\"loginAlias\"\n />\n </c8y-form-group>\n\n <c8y-form-group [hasWarning]=\"!user.email\">\n <label translate for=\"userEmail\">Email</label>\n <input\n id=\"userEmail\"\n class=\"form-control\"\n type=\"email\"\n name=\"email\"\n [maxlength]=\"254\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. joe.doe@example.com`LOCALIZE`' | translate }}\"\n [(ngModel)]=\"user.email\"\n email\n [required]=\"true\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userFirstName\">First name</label>\n <input\n id=\"userFirstName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Joe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"firstName\"\n [(ngModel)]=\"user.firstName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label translate for=\"userLastName\">Last name</label>\n <input\n id=\"userLastName\"\n class=\"form-control\"\n autocomplete=\"off\"\n placeholder=\"{{ 'e.g. Doe`LOCALIZE`' | translate }}\"\n maxlength=\"50\"\n name=\"lastName\"\n [(ngModel)]=\"user.lastName\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n </div>\n </div>\n\n <c8y-form-group>\n <label translate for=\"userTelephone\">Telephone</label>\n <input\n id=\"userTelephone\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"phone\"\n maxlength=\"254\"\n [(ngModel)]=\"user.phone\"\n placeholder=\"{{ 'e.g. +49 9 876 543 210`LOCALIZE`' | translate }}\"\n c8yPhoneValidation\n [required]=\"isPhoneRequired\"\n [disabled]=\"userIsExternal\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"p-t-16 separator-top\" *ngIf=\"showProductExperienceOptions\">\n <label translate>Product experience</label>\n <label class=\"c8y-switch\" for=\"productUsageTracking\">\n <input\n id=\"productUsageTracking\"\n name=\"productUsageTracking\"\n type=\"checkbox\"\n [(ngModel)]=\"isUsageTrackingEnabled\"\n />\n <span></span>\n {{ 'Enable personalized product experience tracking' | translate }}\n </label>\n <ng-container *ngIf=\"isUsageTrackingEnabled\">\n <label class=\"c8y-switch m-l-0\" for=\"userEngagementPreference\">\n <input\n id=\"userEngagementPreference\"\n name=\"userEngagementPreference\"\n type=\"checkbox\"\n [(ngModel)]=\"isUserEngagementPreferenceEnabled\"\n />\n <span></span>\n {{ 'Enable in-product information & communication' | translate }}\n </label>\n </ng-container>\n </c8y-form-group>\n\n <div class=\"form-group p-t-16 separator-top\" *ngIf=\"!userIsExternal\">\n <label class=\"control-label\">{{ 'Login options' | translate }}</label>\n <c8y-new-password (password)=\"onNewPasswordChanged($event)\"></c8y-new-password>\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Set up two-factor authentication' | translate }}\"\n (click)=\"setupTotp()\"\n *ngIf=\"userCanSetupTotp && !userHasActiveTotp && isTfaEnabled\"\n >\n {{ 'Set up two-factor authentication' | translate }}\n </button>\n </div>\n\n <c8y-form-group *ngIf=\"!!(state.state$ | async).newsletter\">\n <label translate>Newsletter</label>\n <label\n title=\"{{ 'Send me information about outages, maintenance or updates.' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n name=\"newsletter\"\n [(ngModel)]=\"user.newsletter\"\n [disabled]=\"userIsExternal\"\n />\n <span></span>\n <span>\n {{ 'Send me information about outages, maintenance or updates.' | translate }}\n </span>\n </label>\n </c8y-form-group>\n </div>\n <div class=\"modal-footer separator-top bg-level-0 sticky-bottom\">\n <button\n class=\"btn btn-default\"\n type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Save' | translate }}\"\n [disabled]=\"!userForm.form.valid || userForm.form.pristine || loading\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n" }]
20239
- }], ctorParameters: () => [{ type: AppStateService }, { type: TranslateService }, { type: i1$7.BsModalService }, { type: AlertService }, { type: i1.UserService }, { type: i1.TenantLoginOptionsService }, { type: i1.TenantService }], propDecorators: { loading: [{
20240
- type: Input
20241
- }], user: [{
20242
- type: Input
20243
- }], showProductExperienceOptions: [{
20244
- type: Input
20245
- }], isUsageTrackingEnabled: [{
20246
- type: Input
20247
- }], isUserEngagementPreferenceEnabled: [{
20248
- type: Input
20249
- }], onUser: [{
20250
- type: Output
20251
- }], onUsageTrackingChange: [{
20252
- type: Output
20253
- }], onUserEngagementPreferenceChange: [{
20254
- type: Output
20255
- }], onCancel: [{
20256
- type: Output
20257
- }] } });
20253
+ getCurrentThemePreference() {
20254
+ const value = getThemePreference();
20255
+ if (value === 'system' || value === 'dark') {
20256
+ return value;
20257
+ }
20258
+ return 'light';
20259
+ }
20260
+ getUsersSystemPreferenceForTheme$() {
20261
+ return fromEvent(window.matchMedia('(prefers-color-scheme: dark)'), 'change').pipe(startWith(window.matchMedia('(prefers-color-scheme: dark)')), map((e) => (e.matches ? 'dark' : 'light')));
20262
+ }
20263
+ changeUserPreference(preference) {
20264
+ setThemePreference(preference);
20265
+ this._userSelectedThemePreference$.next(preference);
20266
+ this.applyTheme(preference);
20267
+ }
20268
+ temporaryChangeTheme(preference) {
20269
+ this._temporaryThemePreference$.next(preference);
20270
+ this.applyTheme(preference);
20271
+ }
20272
+ resetTemporaryTheme() {
20273
+ this._temporaryThemePreference$.next('none');
20274
+ this.applyTheme(this.getCurrentThemePreference());
20275
+ }
20276
+ applyTheme(preference) {
20277
+ applyTheme(preference);
20278
+ }
20279
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, deps: [{ token: OptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
20280
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, providedIn: 'root' }); }
20281
+ }
20282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeSwitcherService, decorators: [{
20283
+ type: Injectable,
20284
+ args: [{
20285
+ providedIn: 'root'
20286
+ }]
20287
+ }], ctorParameters: () => [{ type: OptionsService }] });
20258
20288
 
20259
- class UserEditModalComponent {
20260
- constructor(modal, user, ui, auth, client, alert, userPreferences, c8yModalService, gainsightService, cookieBannerService, passwordService, userEngagementsService) {
20261
- this.modal = modal;
20262
- this.user = user;
20289
+ class UiSettingsComponent {
20290
+ constructor(translate, state, ui, userPreferences, c8yModalService, headerService, themeSwitcher) {
20291
+ this.translate = translate;
20292
+ this.state = state;
20263
20293
  this.ui = ui;
20264
- this.auth = auth;
20265
- this.client = client;
20266
- this.alert = alert;
20267
20294
  this.userPreferences = userPreferences;
20268
20295
  this.c8yModalService = c8yModalService;
20269
- this.gainsightService = gainsightService;
20270
- this.cookieBannerService = cookieBannerService;
20271
- this.passwordService = passwordService;
20272
- this.userEngagementsService = userEngagementsService;
20273
- this.loading = false;
20274
- this.showProductExperienceOptions = false;
20275
- }
20276
- async ngOnInit() {
20277
- this.updateUserInAppState();
20278
- await this.setInitialProductExperienceOptions();
20279
- }
20280
- /**
20281
- * Initializes product experience options for the user.
20282
- *
20283
- * This function performs the following operations:
20284
- * - Determines if the user has the permission to edit product experience options.
20285
- * - If the user has the permission and functional cookies are enabled:
20286
- * - Checks whether personalized product experience tracking is active.
20287
- * - Checks whether in-product information and communication is active.
20288
- */
20289
- async setInitialProductExperienceOptions() {
20290
- this.showProductExperienceOptions =
20291
- await this.gainsightService.canEditProductExperienceSettings();
20292
- if (this.showProductExperienceOptions && this.cookieBannerService.isFunctionalCookieEnabled()) {
20293
- // Enable personalized product experience tracking option
20294
- this.currentUsageTrackingState =
20295
- !(await this.gainsightService.isGainsightPreferenceDisabledInUserPreferences(this.gainsightService.USER_PREFERENCES_GAINSIGHT_KEY));
20296
- // Enable in-product information & communication option
20297
- this.currentUserEngagementPreferenceInitialState =
20298
- this.userEngagementsService.userEngagementsEnabled$.value;
20299
- }
20300
- }
20301
- async onDismiss() {
20302
- this.modal.hide();
20296
+ this.headerService = headerService;
20297
+ this.themeSwitcher = themeSwitcher;
20298
+ this.destroyed$ = new Subject();
20299
+ this.currentLang = this.ui.state.lang;
20300
+ this.ui.state$
20301
+ .pipe(filter(({ lang }) => lang !== this.currentLang), takeUntil(this.destroyed$), first$1())
20302
+ .subscribe(({ lang }) => (this.currentLang = lang));
20303
+ this.open$ = this.headerService.rightDrawerOpen$;
20303
20304
  }
20304
- onUsageTrackingChange(isEnabled) {
20305
- this.usageTrackingState = isEnabled;
20305
+ ngOnInit() {
20306
+ this.languages = this.state.state.langs.map(l => ({
20307
+ lang: l,
20308
+ nativeLanguage: this.translate.getNativeLanguage(l)
20309
+ }));
20306
20310
  }
20307
- onUserEngagementPreferenceChange(isEnabled) {
20308
- this.userEngagementPreferenceNewState = isEnabled;
20311
+ ngOnDestroy() {
20312
+ this.destroyed$.next();
20313
+ this.destroyed$.complete();
20309
20314
  }
20310
- async updateAndClose(user) {
20311
- this.loading = true;
20312
- try {
20313
- const passwordChanged = Boolean(user.password);
20314
- const isExternalUser = user.customProperties.userOrigin === 'OAUTH2';
20315
- if (!isExternalUser && passwordChanged) {
20316
- const currentPassword = await this.passwordService.currentPassword().toPromise();
20317
- if (!currentPassword) {
20318
- return;
20319
- }
20320
- await this.user.changeCurrentUserPassword(user.password, currentPassword);
20321
- this.updateCredentials(user.password);
20322
- }
20323
- await this.updateProductExperienceOptions();
20324
- if (user.customProperties.userOrigin !== 'OAUTH2') {
20325
- await this.user.updateCurrent(omit(user, 'password'));
20326
- await this.updateUserInAppState();
20327
- }
20328
- this.modal.hide();
20329
- this.alert.success(gettext$1('User saved.'));
20330
- }
20331
- catch (e) {
20332
- if (e) {
20333
- this.alert.addServerFailure(e);
20334
- }
20315
+ async onLanguageChange(changedLang) {
20316
+ if (!changedLang) {
20317
+ return;
20335
20318
  }
20336
- finally {
20337
- this.loading = false;
20319
+ await this.translate.switchToLanguage(changedLang);
20320
+ if (await this.persistLanguage(changedLang)) {
20321
+ location.reload();
20338
20322
  }
20339
20323
  }
20340
- async gainsightTrackingAppReload() {
20324
+ async persistLanguage(lang) {
20325
+ let shouldReload = true;
20341
20326
  try {
20342
- await this.c8yModalService.confirm(gettext$1('Reload required'), gettext$1('To change the tracking option in the entire application, you need to reload the page. If you have any unsaved changes, you can reload later. How would you like to proceed?'), Status.WARNING, {
20327
+ await this.c8yModalService.confirm(gettext$1('Reload recommended'), gettext$1('To change the language in the entire application, we recommend you to reload the page. If you have any unsaved changes, you can reload later. How would you like to proceed?'), Status.WARNING, {
20343
20328
  ok: gettext$1('Reload now'),
20344
20329
  cancel: gettext$1('Reload later')
20345
20330
  });
20346
- location.reload();
20347
20331
  }
20348
20332
  catch (ex) {
20349
- // do nothing
20333
+ shouldReload = false;
20334
+ }
20335
+ finally {
20336
+ this.translate.saveInLocalStorage(lang);
20337
+ await this.userPreferences.set('language', lang);
20338
+ this.currentLang = lang;
20350
20339
  }
20340
+ return shouldReload;
20351
20341
  }
20352
- async updateProductExperienceOptions() {
20353
- this.updateUserEngagementsPreference();
20354
- await this.updateTrackingOption();
20342
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsComponent, deps: [{ token: TranslateService }, { token: AppStateService }, { token: AppStateService }, { token: UserPreferencesService }, { token: ModalService }, { token: HeaderService }, { token: ThemeSwitcherService }], target: i0.ɵɵFactoryTarget.Component }); }
20343
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UiSettingsComponent, isStandalone: true, selector: "c8y-ui-settings", ngImport: i0, template: "<div class=\"separator-top p-t-8 p-b-8\">\n <div class=\"c8y-right-drawer__item sticky-top\">\n <i c8yIcon=\"eyedropper\"></i>\n <span class=\"text-bold\">{{ 'UI settings' | translate }}</span>\n </div>\n\n <div\n class=\"p-l-16 p-r-16 p-b-16\"\n *ngIf=\"themeSwitcher.darkThemeAvailable$ | async\"\n >\n <p translate>Theme</p>\n <div\n class=\"c8y-switch-multistate\"\n *ngIf=\"themeSwitcher.userSelectedThemePreference$ | async as themePreference\"\n >\n <ng-container *ngFor=\"let themeOption of themeSwitcher.themeOptions; index as i\">\n <input\n [attr.aria-label]=\"themeOption.label\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n name=\"theme-switcher\"\n type=\"radio\"\n [id]=\"'theme-option-' + i\"\n [disabled]=\"themeSwitcher.disableThemeSelection$ | async\"\n [checked]=\"themePreference === themeOption.value\"\n (click)=\"themeSwitcher.changeUserPreference(themeOption.value)\"\n />\n <label\n title=\"{{ themeOption.label | translate }}\"\n [for]=\"'theme-option-' + i\"\n >\n <i [c8yIcon]=\"themeOption.icon\"></i>\n </label>\n </ng-container>\n <div class=\"c8y-switch-multistate__handle\"></div>\n </div>\n </div>\n\n <div class=\"form-group p-l-16 p-r-16\">\n <label\n for=\"userLang\"\n translate\n >\n Language\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n id=\"userLang\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n #selectLang\n [ngModel]=\"currentLang\"\n (change)=\"onLanguageChange(selectLang.value)\"\n >\n <option\n *ngFor=\"let language of languages\"\n [value]=\"language.lang\"\n >\n {{ language.nativeLanguage }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$8.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$8.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
20344
+ }
20345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsComponent, decorators: [{
20346
+ type: Component,
20347
+ args: [{ selector: 'c8y-ui-settings', standalone: true, imports: [
20348
+ IconDirective,
20349
+ NgIf,
20350
+ C8yTranslateDirective,
20351
+ NgFor,
20352
+ FormsModule$1,
20353
+ C8yTranslatePipe,
20354
+ AsyncPipe
20355
+ ], template: "<div class=\"separator-top p-t-8 p-b-8\">\n <div class=\"c8y-right-drawer__item sticky-top\">\n <i c8yIcon=\"eyedropper\"></i>\n <span class=\"text-bold\">{{ 'UI settings' | translate }}</span>\n </div>\n\n <div\n class=\"p-l-16 p-r-16 p-b-16\"\n *ngIf=\"themeSwitcher.darkThemeAvailable$ | async\"\n >\n <p translate>Theme</p>\n <div\n class=\"c8y-switch-multistate\"\n *ngIf=\"themeSwitcher.userSelectedThemePreference$ | async as themePreference\"\n >\n <ng-container *ngFor=\"let themeOption of themeSwitcher.themeOptions; index as i\">\n <input\n [attr.aria-label]=\"themeOption.label\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n name=\"theme-switcher\"\n type=\"radio\"\n [id]=\"'theme-option-' + i\"\n [disabled]=\"themeSwitcher.disableThemeSelection$ | async\"\n [checked]=\"themePreference === themeOption.value\"\n (click)=\"themeSwitcher.changeUserPreference(themeOption.value)\"\n />\n <label\n title=\"{{ themeOption.label | translate }}\"\n [for]=\"'theme-option-' + i\"\n >\n <i [c8yIcon]=\"themeOption.icon\"></i>\n </label>\n </ng-container>\n <div class=\"c8y-switch-multistate__handle\"></div>\n </div>\n </div>\n\n <div class=\"form-group p-l-16 p-r-16\">\n <label\n for=\"userLang\"\n translate\n >\n Language\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n id=\"userLang\"\n tabindex=\"{{ (open$ | async) ? '0' : '-1' }}\"\n #selectLang\n [ngModel]=\"currentLang\"\n (change)=\"onLanguageChange(selectLang.value)\"\n >\n <option\n *ngFor=\"let language of languages\"\n [value]=\"language.lang\"\n >\n {{ language.nativeLanguage }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n</div>\n" }]
20356
+ }], ctorParameters: () => [{ type: TranslateService }, { type: AppStateService }, { type: AppStateService }, { type: UserPreferencesService }, { type: ModalService }, { type: HeaderService }, { type: ThemeSwitcherService }] });
20357
+
20358
+ class UiSettingsModule {
20359
+ static providers() {
20360
+ return [
20361
+ hookDrawer({
20362
+ component: UiSettingsComponent,
20363
+ position: 'right',
20364
+ priority: 90,
20365
+ id: 'uiSettings'
20366
+ })
20367
+ ];
20355
20368
  }
20356
- /**
20357
- * Updates the user engagement preference if it has changed from the initial state.
20358
- * Calls the user engagements service to update the preference.
20359
- *
20360
- * The update only occurs if the current preference differs from the new state.
20361
- */
20362
- updateUserEngagementsPreference() {
20363
- if (this.currentUserEngagementPreferenceInitialState !== this.userEngagementPreferenceNewState) {
20364
- this.userEngagementsService.updateUserEngagementPreference(this.userEngagementPreferenceNewState);
20365
- }
20369
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
20370
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, imports: [CommonModule, FormsModule$1, UiSettingsComponent], exports: [UiSettingsComponent] }); }
20371
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, imports: [CommonModule, FormsModule$1, UiSettingsComponent] }); }
20372
+ }
20373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UiSettingsModule, decorators: [{
20374
+ type: NgModule,
20375
+ args: [{
20376
+ imports: [CommonModule, FormsModule$1, UiSettingsComponent],
20377
+ exports: [UiSettingsComponent]
20378
+ }]
20379
+ }] });
20380
+
20381
+ class UserMenuItemComponent {
20382
+ constructor(userService, headerService) {
20383
+ this.userService = userService;
20384
+ this.headerService = headerService;
20385
+ this.priority = 0;
20386
+ this.click = new EventEmitter();
20387
+ this.open$ = this.headerService.rightDrawerOpen$;
20366
20388
  }
20367
- /**
20368
- * Asynchronously updates the tracking option for user preferences.
20369
- * If the current usage tracking state differs from the new state,
20370
- * it updates the Gainsight preferences and sets a functional cookie
20371
- * before triggering a reload of the application.
20372
- */
20373
- async updateTrackingOption() {
20374
- if (this.currentUsageTrackingState !== this.usageTrackingState) {
20375
- await this.userPreferences.set(this.gainsightService.USER_PREFERENCES_GAINSIGHT_KEY, this.usageTrackingState);
20376
- this.gainsightService.setFunctionalCookie(this.usageTrackingState);
20377
- await this.gainsightTrackingAppReload();
20378
- }
20389
+ ngAfterViewInit() {
20390
+ this.viewInitTimeout = setTimeout(() => this.userService.add(this));
20379
20391
  }
20380
- async updateUserInAppState() {
20381
- const currentUserResult = await this.user.current();
20382
- this.ui.currentUser.next(currentUserResult.data);
20392
+ ngOnDestroy() {
20393
+ clearTimeout(this.viewInitTimeout);
20394
+ this.userService.remove(this);
20383
20395
  }
20384
- updateCredentials(password) {
20385
- const newCredentials = {
20386
- password,
20387
- user: this.ui.currentUser.value.id,
20388
- tenant: this.client.tenant
20389
- };
20390
- this.auth.updateCredentials(newCredentials);
20396
+ onClick() {
20397
+ this.click.emit(this);
20391
20398
  }
20392
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditModalComponent, deps: [{ token: i1$7.BsModalRef }, { token: i1.UserService }, { token: AppStateService }, { token: i1.BasicAuth }, { token: i1.FetchClient }, { token: AlertService }, { token: UserPreferencesService }, { token: ModalService }, { token: GainsightService }, { token: CookieBannerService }, { token: PasswordService }, { token: UserEngagementsService }], target: i0.ɵɵFactoryTarget.Component }); }
20393
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserEditModalComponent, isStandalone: true, selector: "c8y-user-edit-modal", ngImport: i0, template: "<c8y-modal\n [title]=\"'Edit user' | translate\"\n [customFooter]=\"true\"\n (onDismiss)=\"onDismiss()\"\n>\n <c8y-user-edit\n [user]=\"ui.currentUser | async\"\n [loading]=\"loading\"\n [isUsageTrackingEnabled]=\"currentUsageTrackingState\"\n [isUserEngagementPreferenceEnabled]=\"currentUserEngagementPreferenceInitialState\"\n [showProductExperienceOptions]=\"showProductExperienceOptions\"\n (onUsageTrackingChange)=\"onUsageTrackingChange($event)\"\n (onUserEngagementPreferenceChange)=\"onUserEngagementPreferenceChange($event)\"\n (onUser)=\"updateAndClose($event)\"\n (onCancel)=\"onDismiss()\"\n ></c8y-user-edit>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: UserEditComponent, selector: "c8y-user-edit", inputs: ["loading", "user", "showProductExperienceOptions", "isUsageTrackingEnabled", "isUserEngagementPreferenceEnabled"], outputs: ["onUser", "onUsageTrackingChange", "onUserEngagementPreferenceChange", "onCancel"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
20399
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserMenuItemComponent, deps: [{ token: UserMenuService }, { token: HeaderService }], target: i0.ɵɵFactoryTarget.Component }); }
20400
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: UserMenuItemComponent, isStandalone: true, selector: "c8y-user-menu-item", inputs: { icon: "icon", label: "label", link: "link", target: "target", priority: "priority", dataCy: "dataCy" }, outputs: { click: "click" }, viewQueries: [{ propertyName: "template", first: true, predicate: ["template"], descendants: true }], ngImport: i0, template: "<ng-template #template>\n <li>\n <a\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n *ngIf=\"link\"\n [attr.href]=\"link\"\n [attr.target]=\"target\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </a>\n <button\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n type=\"button\"\n *ngIf=\"!link\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </button>\n </li>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
20394
20401
  }
20395
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserEditModalComponent, decorators: [{
20402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UserMenuItemComponent, decorators: [{
20396
20403
  type: Component,
20397
- args: [{ selector: 'c8y-user-edit-modal', standalone: true, imports: [ModalComponent, UserEditComponent, C8yTranslatePipe, AsyncPipe], template: "<c8y-modal\n [title]=\"'Edit user' | translate\"\n [customFooter]=\"true\"\n (onDismiss)=\"onDismiss()\"\n>\n <c8y-user-edit\n [user]=\"ui.currentUser | async\"\n [loading]=\"loading\"\n [isUsageTrackingEnabled]=\"currentUsageTrackingState\"\n [isUserEngagementPreferenceEnabled]=\"currentUserEngagementPreferenceInitialState\"\n [showProductExperienceOptions]=\"showProductExperienceOptions\"\n (onUsageTrackingChange)=\"onUsageTrackingChange($event)\"\n (onUserEngagementPreferenceChange)=\"onUserEngagementPreferenceChange($event)\"\n (onUser)=\"updateAndClose($event)\"\n (onCancel)=\"onDismiss()\"\n ></c8y-user-edit>\n</c8y-modal>\n" }]
20398
- }], ctorParameters: () => [{ type: i1$7.BsModalRef }, { type: i1.UserService }, { type: AppStateService }, { type: i1.BasicAuth }, { type: i1.FetchClient }, { type: AlertService }, { type: UserPreferencesService }, { type: ModalService }, { type: GainsightService }, { type: CookieBannerService }, { type: PasswordService }, { type: UserEngagementsService }] });
20404
+ args: [{ selector: 'c8y-user-menu-item', standalone: true, imports: [NgIf, C8yTranslatePipe, AsyncPipe], template: "<ng-template #template>\n <li>\n <a\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n *ngIf=\"link\"\n [attr.href]=\"link\"\n [attr.target]=\"target\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </a>\n <button\n class=\"c8y-right-drawer__link\"\n [attr.tabindex]=\"(open$ | async) ? '0' : '-1'\"\n type=\"button\"\n *ngIf=\"!link\"\n (click)=\"onClick()\"\n [attr.data-cy]=\"dataCy\"\n >\n {{ label | translate }}\n <ng-content></ng-content>\n </button>\n </li>\n</ng-template>\n" }]
20405
+ }], ctorParameters: () => [{ type: UserMenuService }, { type: HeaderService }], propDecorators: { icon: [{
20406
+ type: Input
20407
+ }], label: [{
20408
+ type: Input
20409
+ }], link: [{
20410
+ type: Input
20411
+ }], target: [{
20412
+ type: Input
20413
+ }], priority: [{
20414
+ type: Input
20415
+ }], dataCy: [{
20416
+ type: Input
20417
+ }], template: [{
20418
+ type: ViewChild,
20419
+ args: ['template', { static: false }]
20420
+ }], click: [{
20421
+ type: Output
20422
+ }] } });
20399
20423
 
20400
20424
  class UserMenuOutletComponent {
20401
20425
  constructor(ui, bsModalService, authService, userMenu, headerService) {
@@ -22999,7 +23023,7 @@ class FilePickerComponent {
22999
23023
  this.onFilesPicked.emit(this.fileToSave);
23000
23024
  }
23001
23025
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FilePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
23002
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: FilePickerComponent, isStandalone: true, selector: "c8y-file-picker", inputs: { maxAllowedFiles: "maxAllowedFiles", uploadChoice: "uploadChoice", allowedUploadChoices: "allowedUploadChoices", fileUrl: "fileUrl", fileBinary: "fileBinary", config: "config", filePickerIndex: "filePickerIndex", fileUrlPopover: "fileUrlPopover" }, outputs: { onFilesPicked: "onFilesPicked" }, viewQueries: [{ propertyName: "dropArea", first: true, predicate: DropAreaComponent, descendants: true, static: true }], ngImport: i0, template: "<div class=\"form-group\">\n <label\n class=\"c8y-radio\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadBinary\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearInputFromUrl()\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n data-cy=\"file-picker--file-path-input\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadUrl\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearSelectedFiles()\"\n />\n <span></span>\n <span>\n {{ 'Provide a file path' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ fileUrlPopover | translate }}\"\n placement=\"top\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n *ngIf=\"isPopoverUsed()\"\n ></button>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Mark as provided' | translate }}\"\n *ngIf=\"allowedUploadChoices.includes('provided')\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"provided\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"setProvidedOption()\"\n />\n <span></span>\n <span>{{ 'Provided' | translate }}</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ providedPopover | translate }}\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadBinary'\">\n <c8y-form-group class=\"m-0\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n [attr.aria-label]=\"'Drop file or click to browse' | translate\"\n (dropped)=\"onFileDropped($event)\"\n [maxAllowedFiles]=\"maxAllowedFiles\"\n [files]=\"droppedFiles\"\n ></c8y-drop-area>\n </c8y-form-group>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadUrl'\">\n <c8y-form-group class=\"m-0\">\n <div class=\"m-b-4 p-b-8\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n name=\"fileUrl\"\n type=\"text\"\n required\n data-cy=\"file-picker--fileUrl\"\n [(ngModel)]=\"fileUrl\"\n (ngModelChange)=\"onFileUrlChange($event)\"\n maxlength=\"{{ config.maxlength }}\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </div>\n </div>\n </c8y-form-group>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.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$8.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$8.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$9.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
23026
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: FilePickerComponent, isStandalone: true, selector: "c8y-file-picker", inputs: { maxAllowedFiles: "maxAllowedFiles", uploadChoice: "uploadChoice", allowedUploadChoices: "allowedUploadChoices", fileUrl: "fileUrl", fileBinary: "fileBinary", config: "config", filePickerIndex: "filePickerIndex", fileUrlPopover: "fileUrlPopover" }, outputs: { onFilesPicked: "onFilesPicked" }, viewQueries: [{ propertyName: "dropArea", first: true, predicate: DropAreaComponent, descendants: true, static: true }], ngImport: i0, template: "<div class=\"form-group\">\n <label\n class=\"c8y-radio\"\n [class.m-l-8]=\"allowedUploadChoices.includes('provided')\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadBinary\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearInputFromUrl()\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n data-cy=\"file-picker--file-path-input\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadUrl\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearSelectedFiles()\"\n />\n <span></span>\n <span>\n {{ 'Provide a file path' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ fileUrlPopover | translate }}\"\n placement=\"top\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n *ngIf=\"isPopoverUsed()\"\n ></button>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Mark as provided' | translate }}\"\n *ngIf=\"allowedUploadChoices.includes('provided')\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"provided\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"setProvidedOption()\"\n />\n <span></span>\n <span>{{ 'Provided' | translate }}</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ providedPopover | translate }}\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadBinary'\">\n <c8y-form-group class=\"m-0\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n [attr.aria-label]=\"'Drop file or click to browse' | translate\"\n (dropped)=\"onFileDropped($event)\"\n [maxAllowedFiles]=\"maxAllowedFiles\"\n [files]=\"droppedFiles\"\n ></c8y-drop-area>\n </c8y-form-group>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadUrl'\">\n <c8y-form-group class=\"m-0\">\n <div class=\"m-b-4 p-b-8\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n name=\"fileUrl\"\n type=\"text\"\n required\n data-cy=\"file-picker--fileUrl\"\n [(ngModel)]=\"fileUrl\"\n (ngModelChange)=\"onFileUrlChange($event)\"\n maxlength=\"{{ config.maxlength }}\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </div>\n </div>\n </c8y-form-group>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.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$8.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$8.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$9.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
23003
23027
  }
23004
23028
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FilePickerComponent, decorators: [{
23005
23029
  type: Component,
@@ -23012,7 +23036,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
23012
23036
  IconDirective,
23013
23037
  RequiredInputPlaceholderDirective,
23014
23038
  C8yTranslatePipe
23015
- ], template: "<div class=\"form-group\">\n <label\n class=\"c8y-radio\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadBinary\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearInputFromUrl()\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n data-cy=\"file-picker--file-path-input\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadUrl\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearSelectedFiles()\"\n />\n <span></span>\n <span>\n {{ 'Provide a file path' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ fileUrlPopover | translate }}\"\n placement=\"top\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n *ngIf=\"isPopoverUsed()\"\n ></button>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Mark as provided' | translate }}\"\n *ngIf=\"allowedUploadChoices.includes('provided')\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"provided\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"setProvidedOption()\"\n />\n <span></span>\n <span>{{ 'Provided' | translate }}</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ providedPopover | translate }}\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadBinary'\">\n <c8y-form-group class=\"m-0\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n [attr.aria-label]=\"'Drop file or click to browse' | translate\"\n (dropped)=\"onFileDropped($event)\"\n [maxAllowedFiles]=\"maxAllowedFiles\"\n [files]=\"droppedFiles\"\n ></c8y-drop-area>\n </c8y-form-group>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadUrl'\">\n <c8y-form-group class=\"m-0\">\n <div class=\"m-b-4 p-b-8\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n name=\"fileUrl\"\n type=\"text\"\n required\n data-cy=\"file-picker--fileUrl\"\n [(ngModel)]=\"fileUrl\"\n (ngModelChange)=\"onFileUrlChange($event)\"\n maxlength=\"{{ config.maxlength }}\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </div>\n </div>\n </c8y-form-group>\n</div>\n" }]
23039
+ ], template: "<div class=\"form-group\">\n <label\n class=\"c8y-radio\"\n [class.m-l-8]=\"allowedUploadChoices.includes('provided')\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadBinary\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearInputFromUrl()\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n data-cy=\"file-picker--file-path-input\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"uploadUrl\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"clearSelectedFiles()\"\n />\n <span></span>\n <span>\n {{ 'Provide a file path' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ fileUrlPopover | translate }}\"\n placement=\"top\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n *ngIf=\"isPopoverUsed()\"\n ></button>\n </label>\n <label\n class=\"c8y-radio m-l-8\"\n title=\"{{ 'Mark as provided' | translate }}\"\n *ngIf=\"allowedUploadChoices.includes('provided')\"\n >\n <input\n name=\"uploadChoice-{{ filePickerIndex }}\"\n type=\"radio\"\n value=\"provided\"\n #radio\n [(ngModel)]=\"uploadChoice\"\n (click)=\"setProvidedOption()\"\n />\n <span></span>\n <span>{{ 'Provided' | translate }}</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ providedPopover | translate }}\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadBinary'\">\n <c8y-form-group class=\"m-0\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n [attr.aria-label]=\"'Drop file or click to browse' | translate\"\n (dropped)=\"onFileDropped($event)\"\n [maxAllowedFiles]=\"maxAllowedFiles\"\n [files]=\"droppedFiles\"\n ></c8y-drop-area>\n </c8y-form-group>\n</div>\n\n<div [hidden]=\"uploadChoice !== 'uploadUrl'\">\n <c8y-form-group class=\"m-0\">\n <div class=\"m-b-4 p-b-8\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n name=\"fileUrl\"\n type=\"text\"\n required\n data-cy=\"file-picker--fileUrl\"\n [(ngModel)]=\"fileUrl\"\n (ngModelChange)=\"onFileUrlChange($event)\"\n maxlength=\"{{ config.maxlength }}\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </div>\n </div>\n </c8y-form-group>\n</div>\n" }]
23016
23040
  }], propDecorators: { dropArea: [{
23017
23041
  type: ViewChild,
23018
23042
  args: [DropAreaComponent, { static: true }]